From 520ebb7e3c87a40c91e04ccb7794199b553adbb9 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:26:25 -0700 Subject: [PATCH 001/101] feat: current mode with NEC breaker limit lines and panel amperage Show amps instead of watts when chart_metric is set to current. Fix Y-axis to 0-125% of breaker rating with NEC reference lines at 80% (yellow, continuous load limit) and 100% (red, breaker rating). Add total current stat to panel header from main meter amperage attribute. --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++ src/card/span-panel-card.js | 40 ++++++++++++++++++++++++++++++++++--- src/chart/chart-options.js | 34 ++++++++++++++++++++++++++++++- src/chart/chart-update.js | 4 ++-- src/constants.js | 2 +- 5 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5d3e98b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +## 0.8.9 + +- Show amps instead of watts above circuit graphs when chart metric is set to `current` +- Fix Y-axis scale to 0–125% of breaker rating in current mode +- Add red horizontal line at 100% of breaker rating (NEC trip point) +- Add yellow dashed line at 80% of breaker rating (NEC continuous load limit) +- Add total current (amps) stat to panel header + +## 0.8.8 + +- Chart Y-axis formatting uses absolute values for power metric + +## 0.8.7 + +- Use dynamic `panel_size` from topology instead of hardcoded 32 + +## 0.8.6 + +- Fix editor storing default 5 minutes when user only changes days/hours +- Use statistics API for long-duration charts +- Fix 0-minute config bug + +## 0.8.5 + +- Add project tooling, CI, and HACS support + +## 0.8.4 + +- Initial SPAN Panel custom Lovelace card diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index f5bd20e..8452bc0 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -335,9 +335,20 @@ export class SpanPanelCard extends HTMLElement { const consumptionEl = root.querySelector(".stat-consumption .stat-value"); if (consumptionEl) consumptionEl.textContent = formatKw(totalConsumption); + + const currentEl = root.querySelector(".stat-current .stat-value"); + if (currentEl) { + const panelPowerEid = this._findPanelEntity("current_power"); + const panelPowerState = panelPowerEid ? hass.states[panelPowerEid] : null; + const amperage = panelPowerState ? parseFloat(panelPowerState.attributes?.amperage) : NaN; + currentEl.textContent = Number.isFinite(amperage) ? amperage.toFixed(1) : "--"; + } const solarEl = root.querySelector(".stat-solar .stat-value"); if (solarEl) solarEl.textContent = solarProduction > 0 ? formatKw(solarProduction) : "--"; + const chartMetric = getChartMetric(this._config); + const showCurrent = chartMetric.entityRole === "current"; + for (const [uuid, circuit] of Object.entries(topo.circuits)) { const slot = root.querySelector(`[data-uuid="${uuid}"]`); if (!slot) continue; @@ -353,7 +364,14 @@ export class SpanPanelCard extends HTMLElement { const powerVal = slot.querySelector(".power-value"); if (powerVal) { - powerVal.innerHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; + if (showCurrent) { + const currentEid = circuit.entities?.current; + const currentState = currentEid ? hass.states[currentEid] : null; + const amps = currentState ? parseFloat(currentState.state) || 0 : 0; + powerVal.innerHTML = `${chartMetric.format(amps)}A`; + } else { + powerVal.innerHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; + } } const toggle = slot.querySelector(".toggle-pill"); @@ -370,7 +388,7 @@ export class SpanPanelCard extends HTMLElement { if (chartContainer) { const history = this._powerHistory.get(uuid) || []; const h = slot.classList.contains("circuit-col-span") ? 200 : 100; - updateChart(chartContainer, hass, history, durationMs, getChartMetric(this._config), isProducer, h); + updateChart(chartContainer, hass, history, durationMs, chartMetric, isProducer, h, circuit.breaker_rating_a); } } @@ -491,6 +509,10 @@ export class SpanPanelCard extends HTMLElement { Panel consumption
0kW
+
+ Total current +
--A
+
Solar production
--kW
@@ -709,6 +731,18 @@ export class SpanPanelCard extends HTMLElement { const breakerLabel = breakerAmps ? `${Math.round(breakerAmps)}A` : ""; const name = escapeHtml(circuit.name || "Unknown"); + const chartMetric = getChartMetric(this._config); + const showCurrent = chartMetric.entityRole === "current"; + let valueHTML; + if (showCurrent) { + const currentEid = circuit.entities?.current; + const currentState = currentEid ? hass.states[currentEid] : null; + const amps = currentState ? parseFloat(currentState.state) || 0 : 0; + valueHTML = `${chartMetric.format(amps)}A`; + } else { + valueHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; + } + const rowSpan = layout === "col-span" ? `${row} / span 2` : `${row}`; const layoutClass = layout === "row-span" ? "circuit-row-span" : layout === "col-span" ? "circuit-col-span" : ""; @@ -723,7 +757,7 @@ export class SpanPanelCard extends HTMLElement {
- ${formatPowerSigned(powerW)}${formatPowerUnit(powerW)} + ${valueHTML} ${ circuit.is_user_controllable !== false && circuit.entities?.switch diff --git a/src/chart/chart-options.js b/src/chart/chart-options.js index 410ff93..73c0741 100644 --- a/src/chart/chart-options.js +++ b/src/chart/chart-options.js @@ -1,6 +1,6 @@ import { CHART_METRICS, DEFAULT_CHART_METRIC } from "../constants.js"; -export function buildChartOptions(history, durationMs, metric, isProducer) { +export function buildChartOptions(history, durationMs, metric, isProducer, breakerRatingA) { if (!metric) metric = CHART_METRICS[DEFAULT_CHART_METRIC]; const accentRgb = isProducer ? "140, 160, 220" : "77, 217, 175"; const accentColor = `rgb(${accentRgb})`; @@ -47,6 +47,38 @@ export function buildChartOptions(history, durationMs, metric, isProducer) { yAxis.max = metric.fixedMax; } + // When displaying current with a known breaker rating, fix Y-axis to 125% + // of the rating and draw a red limit line at 100% (NEC reference). + if (breakerRatingA && metric.entityRole === "current") { + yAxis.min = 0; + yAxis.max = Math.ceil(breakerRatingA * 1.25); + + // 80% NEC continuous load limit (yellow dashed) + series.push({ + type: "line", + data: [ + [startTime, breakerRatingA * 0.8], + [now, breakerRatingA * 0.8], + ], + showSymbol: false, + lineStyle: { width: 1, color: "rgba(255, 200, 40, 0.6)", type: "dashed" }, + itemStyle: { color: "transparent" }, + tooltip: { show: false }, + }); + // 100% breaker rating (red solid) + series.push({ + type: "line", + data: [ + [startTime, breakerRatingA], + [now, breakerRatingA], + ], + showSymbol: false, + lineStyle: { width: 1.5, color: "rgba(255, 60, 60, 0.7)", type: "solid" }, + itemStyle: { color: "transparent" }, + tooltip: { show: false }, + }); + } + const options = { xAxis: { type: "time", diff --git a/src/chart/chart-update.js b/src/chart/chart-update.js index 7f137cb..fd2723f 100644 --- a/src/chart/chart-update.js +++ b/src/chart/chart-update.js @@ -1,7 +1,7 @@ import { buildChartOptions } from "./chart-options.js"; -export function updateChart(container, hass, history, durationMs, metric, isProducer, heightPx) { - const { options, series } = buildChartOptions(history, durationMs, metric, isProducer); +export function updateChart(container, hass, history, durationMs, metric, isProducer, heightPx, breakerRatingA) { + const { options, series } = buildChartOptions(history, durationMs, metric, isProducer, breakerRatingA); let chart = container.querySelector("ha-chart-base"); if (!chart) { chart = document.createElement("ha-chart-base"); diff --git a/src/constants.js b/src/constants.js index f4fab2b..7384239 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,4 +1,4 @@ -export const CARD_VERSION = "0.8.8"; +export const CARD_VERSION = "0.8.9"; // ── Defaults ──────────────────────────────────────────────────────────────── From 1a96aa831b3266dc8946723088dd66375e79ad10 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:23:41 -0700 Subject: [PATCH 002/101] refactor: extract grid renderer to core module --- src/card/span-panel-card.js | 135 +---------------------------- src/core/grid-renderer.js | 165 ++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 133 deletions(-) create mode 100644 src/core/grid-renderer.js diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 8452bc0..5ffe979 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -12,8 +12,8 @@ import { import { escapeHtml } from "../helpers/sanitize.js"; import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; import { getHistoryDurationMs, getMaxHistoryPoints, getMinGapMs, recordSample, deduplicateAndTrim } from "../helpers/history.js"; -import { tabToRow, tabToCol, classifyDualTab } from "../helpers/layout.js"; import { getChartMetric, getCircuitChartEntity } from "../helpers/chart.js"; +import { buildGridHTML } from "../core/grid-renderer.js"; import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity, findBatteryCapacityEntity } from "../helpers/entity-finder.js"; import { updateChart } from "../chart/chart-update.js"; import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; @@ -489,7 +489,7 @@ export class SpanPanelCard extends HTMLElement { const panelName = escapeHtml(topo.device_name || "SPAN Panel"); const durationMs = this._durationMs; - const gridHTML = this._buildGridHTML(topo, totalRows, durationMs); + const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config); const subDevHTML = this._buildSubDevicesHTML(topo, hass, durationMs); // Remove previous listener before replacing DOM @@ -548,69 +548,6 @@ export class SpanPanelCard extends HTMLElement { this._updateDOM(); } - _buildGridHTML(topo, totalRows, durationMs) { - const tabMap = new Map(); - const occupiedTabs = new Set(); - - for (const [uuid, circuit] of Object.entries(topo.circuits)) { - const tabs = circuit.tabs; - if (!tabs || tabs.length === 0) continue; - const primaryTab = Math.min(...tabs); - const layout = tabs.length === 1 ? "single" : classifyDualTab(tabs); - tabMap.set(primaryTab, { uuid, circuit, layout }); - for (const t of tabs) occupiedTabs.add(t); - } - - const rowsToSkipLeft = new Set(); - const rowsToSkipRight = new Set(); - - for (const [primaryTab, entry] of tabMap) { - if (entry.layout === "col-span") { - const tabs = entry.circuit.tabs; - const secondaryTab = Math.max(...tabs); - const secondaryRow = tabToRow(secondaryTab); - const col = tabToCol(primaryTab); - if (col === 0) rowsToSkipLeft.add(secondaryRow); - else rowsToSkipRight.add(secondaryRow); - } - } - - let gridHTML = ""; - for (let row = 1; row <= totalRows; row++) { - const leftTab = row * 2 - 1; - const rightTab = row * 2; - const leftEntry = tabMap.get(leftTab); - const rightEntry = tabMap.get(rightTab); - - gridHTML += `
${leftTab}
`; - - if (leftEntry && leftEntry.layout === "row-span") { - gridHTML += this._renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 4", "row-span", durationMs); - gridHTML += `
${rightTab}
`; - continue; - } - - if (!rowsToSkipLeft.has(row)) { - if (leftEntry && (leftEntry.layout === "col-span" || leftEntry.layout === "single")) { - gridHTML += this._renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2", leftEntry.layout, durationMs); - } else if (!occupiedTabs.has(leftTab)) { - gridHTML += this._renderEmptySlot(row, "2"); - } - } - - if (!rowsToSkipRight.has(row)) { - if (rightEntry && (rightEntry.layout === "col-span" || rightEntry.layout === "single")) { - gridHTML += this._renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "3", rightEntry.layout, durationMs); - } else if (!occupiedTabs.has(rightTab)) { - gridHTML += this._renderEmptySlot(row, "3"); - } - } - - gridHTML += `
${rightTab}
`; - } - return gridHTML; - } - _buildSubDevicesHTML(topo, hass, _durationMs) { const showBattery = this._config.show_battery !== false; const showEvse = this._config.show_evse !== false; @@ -715,72 +652,4 @@ export class SpanPanelCard extends HTMLElement { } return ""; } - - _renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs) { - const hass = this._hass; - const entityId = circuit.entities?.power; - const state = entityId ? hass.states[entityId] : null; - const powerW = state ? parseFloat(state.state) || 0 : 0; - const isProducer = circuit.device_type === DEVICE_TYPE_PV || powerW < 0; - - const switchEntityId = circuit.entities?.switch; - const switchState = switchEntityId ? hass.states[switchEntityId] : null; - const isOn = switchState ? switchState.state === "on" : (state?.attributes?.relay_state || circuit.relay_state) === RELAY_STATE_CLOSED; - - const breakerAmps = circuit.breaker_rating_a; - const breakerLabel = breakerAmps ? `${Math.round(breakerAmps)}A` : ""; - const name = escapeHtml(circuit.name || "Unknown"); - - const chartMetric = getChartMetric(this._config); - const showCurrent = chartMetric.entityRole === "current"; - let valueHTML; - if (showCurrent) { - const currentEid = circuit.entities?.current; - const currentState = currentEid ? hass.states[currentEid] : null; - const amps = currentState ? parseFloat(currentState.state) || 0 : 0; - valueHTML = `${chartMetric.format(amps)}A`; - } else { - valueHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; - } - - const rowSpan = layout === "col-span" ? `${row} / span 2` : `${row}`; - const layoutClass = layout === "row-span" ? "circuit-row-span" : layout === "col-span" ? "circuit-col-span" : ""; - - return ` -
-
-
- ${breakerLabel ? `${breakerLabel}` : ""} - ${name} -
-
- - ${valueHTML} - - ${ - circuit.is_user_controllable !== false && circuit.entities?.switch - ? ` -
- ${isOn ? "On" : "Off"} - -
- ` - : "" - } -
-
-
-
- `; - } - - _renderEmptySlot(row, col) { - return ` -
- -
- `; - } } diff --git a/src/core/grid-renderer.js b/src/core/grid-renderer.js new file mode 100644 index 0000000..ebe5920 --- /dev/null +++ b/src/core/grid-renderer.js @@ -0,0 +1,165 @@ +import { escapeHtml } from "../helpers/sanitize.js"; +import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; +import { tabToRow, tabToCol, classifyDualTab } from "../helpers/layout.js"; +import { getChartMetric } from "../helpers/chart.js"; +import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../constants.js"; + +/** + * Build the full grid HTML for the panel breaker grid. + * + * @param {object} topology - The panel topology object. + * @param {number} totalRows - Total number of breaker rows. + * @param {number} durationMs - History duration in milliseconds. + * @param {object} hass - Home Assistant object. + * @param {object} config - Card configuration object. + * @returns {string} HTML string for the grid. + */ +export function buildGridHTML(topology, totalRows, durationMs, hass, config) { + const tabMap = new Map(); + const occupiedTabs = new Set(); + + for (const [uuid, circuit] of Object.entries(topology.circuits)) { + const tabs = circuit.tabs; + if (!tabs || tabs.length === 0) continue; + const primaryTab = Math.min(...tabs); + const layout = tabs.length === 1 ? "single" : classifyDualTab(tabs); + tabMap.set(primaryTab, { uuid, circuit, layout }); + for (const t of tabs) occupiedTabs.add(t); + } + + const rowsToSkipLeft = new Set(); + const rowsToSkipRight = new Set(); + + for (const [primaryTab, entry] of tabMap) { + if (entry.layout === "col-span") { + const tabs = entry.circuit.tabs; + const secondaryTab = Math.max(...tabs); + const secondaryRow = tabToRow(secondaryTab); + const col = tabToCol(primaryTab); + if (col === 0) rowsToSkipLeft.add(secondaryRow); + else rowsToSkipRight.add(secondaryRow); + } + } + + let gridHTML = ""; + for (let row = 1; row <= totalRows; row++) { + const leftTab = row * 2 - 1; + const rightTab = row * 2; + const leftEntry = tabMap.get(leftTab); + const rightEntry = tabMap.get(rightTab); + + gridHTML += `
${leftTab}
`; + + if (leftEntry && leftEntry.layout === "row-span") { + gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 4", "row-span", durationMs, hass, config); + gridHTML += `
${rightTab}
`; + continue; + } + + if (!rowsToSkipLeft.has(row)) { + if (leftEntry && (leftEntry.layout === "col-span" || leftEntry.layout === "single")) { + gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2", leftEntry.layout, durationMs, hass, config); + } else if (!occupiedTabs.has(leftTab)) { + gridHTML += renderEmptySlot(row, "2"); + } + } + + if (!rowsToSkipRight.has(row)) { + if (rightEntry && (rightEntry.layout === "col-span" || rightEntry.layout === "single")) { + gridHTML += renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "3", rightEntry.layout, durationMs, hass, config); + } else if (!occupiedTabs.has(rightTab)) { + gridHTML += renderEmptySlot(row, "3"); + } + } + + gridHTML += `
${rightTab}
`; + } + return gridHTML; +} + +/** + * Render a single circuit breaker slot. + * + * @param {string} uuid - Circuit UUID. + * @param {object} circuit - Circuit data object. + * @param {number} row - Grid row number. + * @param {string} col - Grid column value (CSS grid-column). + * @param {string} layout - Layout type: "single", "row-span", or "col-span". + * @param {number} _durationMs - History duration in milliseconds (reserved for future use). + * @param {object} hass - Home Assistant object. + * @param {object} config - Card configuration object. + * @returns {string} HTML string for the circuit slot. + */ +export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, hass, config) { + const entityId = circuit.entities?.power; + const state = entityId ? hass.states[entityId] : null; + const powerW = state ? parseFloat(state.state) || 0 : 0; + const isProducer = circuit.device_type === DEVICE_TYPE_PV || powerW < 0; + + const switchEntityId = circuit.entities?.switch; + const switchState = switchEntityId ? hass.states[switchEntityId] : null; + const isOn = switchState ? switchState.state === "on" : (state?.attributes?.relay_state || circuit.relay_state) === RELAY_STATE_CLOSED; + + const breakerAmps = circuit.breaker_rating_a; + const breakerLabel = breakerAmps ? `${Math.round(breakerAmps)}A` : ""; + const name = escapeHtml(circuit.name || "Unknown"); + + const chartMetric = getChartMetric(config); + const showCurrent = chartMetric.entityRole === "current"; + let valueHTML; + if (showCurrent) { + const currentEid = circuit.entities?.current; + const currentState = currentEid ? hass.states[currentEid] : null; + const amps = currentState ? parseFloat(currentState.state) || 0 : 0; + valueHTML = `${chartMetric.format(amps)}A`; + } else { + valueHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; + } + + const rowSpan = layout === "col-span" ? `${row} / span 2` : `${row}`; + const layoutClass = layout === "row-span" ? "circuit-row-span" : layout === "col-span" ? "circuit-col-span" : ""; + + return ` +
+
+
+ ${breakerLabel ? `${breakerLabel}` : ""} + ${name} +
+
+ + ${valueHTML} + + ${ + circuit.is_user_controllable !== false && circuit.entities?.switch + ? ` +
+ ${isOn ? "On" : "Off"} + +
+ ` + : "" + } +
+
+
+
+ `; +} + +/** + * Render an empty breaker slot. + * + * @param {number} row - Grid row number. + * @param {string} col - Grid column value (CSS grid-column). + * @returns {string} HTML string for the empty slot. + */ +export function renderEmptySlot(row, col) { + return ` +
+ +
+ `; +} From 26e6735f563b3baa40a9019a1fda0379a695d368 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:54:17 -0700 Subject: [PATCH 003/101] refactor: extract sub-device renderer to core module --- src/card/span-panel-card.js | 111 +------------------------ src/core/sub-device-renderer.js | 139 ++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 108 deletions(-) create mode 100644 src/core/sub-device-renderer.js diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 5ffe979..4e061ec 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -6,7 +6,6 @@ import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SUB_DEVICE_TYPE_BESS, - SUB_DEVICE_TYPE_EVSE, SUB_DEVICE_KEY_PREFIX, } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; @@ -14,7 +13,8 @@ import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format. import { getHistoryDurationMs, getMaxHistoryPoints, getMinGapMs, recordSample, deduplicateAndTrim } from "../helpers/history.js"; import { getChartMetric, getCircuitChartEntity } from "../helpers/chart.js"; import { buildGridHTML } from "../core/grid-renderer.js"; -import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity, findBatteryCapacityEntity } from "../helpers/entity-finder.js"; +import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; +import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity } from "../helpers/entity-finder.js"; import { updateChart } from "../chart/chart-update.js"; import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; import { CARD_STYLES } from "./card-styles.js"; @@ -490,7 +490,7 @@ export class SpanPanelCard extends HTMLElement { const durationMs = this._durationMs; const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config); - const subDevHTML = this._buildSubDevicesHTML(topo, hass, durationMs); + const subDevHTML = buildSubDevicesHTML(topo, hass, this._config, durationMs); // Remove previous listener before replacing DOM this.shadowRoot.removeEventListener("click", this._handleToggleClick); @@ -547,109 +547,4 @@ export class SpanPanelCard extends HTMLElement { this._recordPowerHistory(); this._updateDOM(); } - - _buildSubDevicesHTML(topo, hass, _durationMs) { - const showBattery = this._config.show_battery !== false; - const showEvse = this._config.show_evse !== false; - let subDevHTML = ""; - - if (!topo.sub_devices) return subDevHTML; - - for (const [devId, sub] of Object.entries(topo.sub_devices)) { - if (sub.type === SUB_DEVICE_TYPE_BESS && !showBattery) continue; - if (sub.type === SUB_DEVICE_TYPE_EVSE && !showEvse) continue; - - const label = sub.type === SUB_DEVICE_TYPE_EVSE ? "EV Charger" : sub.type === SUB_DEVICE_TYPE_BESS ? "Battery" : "Sub-device"; - const powerEid = findSubDevicePowerEntity(sub); - const powerState = powerEid ? hass.states[powerEid] : null; - const powerW = powerState ? parseFloat(powerState.state) || 0 : 0; - - const isBess = sub.type === SUB_DEVICE_TYPE_BESS; - const battLevelEid = isBess ? findBatteryLevelEntity(sub) : null; - const battSoeEid = isBess ? findBatterySoeEntity(sub) : null; - const battCapEid = isBess ? findBatteryCapacityEntity(sub) : null; - - const hideEids = new Set([powerEid, battLevelEid, battSoeEid, battCapEid].filter(Boolean)); - const entHTML = this._buildSubEntityHTML(sub, hass, hideEids); - const chartsHTML = this._buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid); - - subDevHTML += ` -
-
- ${escapeHtml(label)} - ${escapeHtml(sub.name || "")} - ${powerEid ? `${formatPowerSigned(powerW)} ${formatPowerUnit(powerW)}` : ""} -
- ${chartsHTML} - ${entHTML} -
- `; - } - return subDevHTML; - } - - _buildSubEntityHTML(sub, hass, hideEids) { - const visibleEnts = this._config.visible_sub_entities || {}; - let entHTML = ""; - if (!sub.entities) return entHTML; - - for (const [entityId, info] of Object.entries(sub.entities)) { - if (hideEids.has(entityId)) continue; - if (visibleEnts[entityId] !== true) continue; - const state = hass.states[entityId]; - if (!state) continue; - let name = info.original_name || state.attributes.friendly_name || entityId; - const devName = sub.name || ""; - if (name.startsWith(devName + " ")) name = name.slice(devName.length + 1); - let displayValue; - if (hass.formatEntityState) { - displayValue = hass.formatEntityState(state); - } else { - displayValue = state.state; - const unit = state.attributes.unit_of_measurement || ""; - if (unit) displayValue += " " + unit; - } - const rawUnit = state.attributes.unit_of_measurement || ""; - if (rawUnit === "Wh") { - const wh = parseFloat(state.state); - if (!isNaN(wh)) displayValue = (wh / 1000).toFixed(1) + " kWh"; - } - entHTML += ` -
- ${escapeHtml(name)}: - ${escapeHtml(displayValue)} -
- `; - } - return entHTML; - } - - _buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid) { - if (isBess) { - const bessCharts = [ - { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soc`, title: "SoC", available: !!battLevelEid }, - { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soe`, title: "SoE", available: !!battSoeEid }, - { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_power`, title: "Power", available: !!powerEid }, - ].filter(c => c.available); - - return ` -
- ${bessCharts - .map( - c => ` -
-
${escapeHtml(c.title)}
-
-
- ` - ) - .join("")} -
- `; - } - if (powerEid) { - return `
`; - } - return ""; - } } diff --git a/src/core/sub-device-renderer.js b/src/core/sub-device-renderer.js new file mode 100644 index 0000000..85b432c --- /dev/null +++ b/src/core/sub-device-renderer.js @@ -0,0 +1,139 @@ +import { escapeHtml } from "../helpers/sanitize.js"; +import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; +import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity, findBatteryCapacityEntity } from "../helpers/entity-finder.js"; +import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_TYPE_EVSE, SUB_DEVICE_KEY_PREFIX } from "../constants.js"; + +/** + * Build the HTML for all sub-devices (BESS, EVSE, etc.) in the topology. + * + * @param {object} topology - Discovered panel topology + * @param {object} hass - Home Assistant object + * @param {object} config - Card configuration + * @param {number} _durationMs - History duration in milliseconds (reserved) + * @returns {string} HTML string + */ +export function buildSubDevicesHTML(topology, hass, config, _durationMs) { + const showBattery = config.show_battery !== false; + const showEvse = config.show_evse !== false; + let subDevHTML = ""; + + if (!topology.sub_devices) return subDevHTML; + + for (const [devId, sub] of Object.entries(topology.sub_devices)) { + if (sub.type === SUB_DEVICE_TYPE_BESS && !showBattery) continue; + if (sub.type === SUB_DEVICE_TYPE_EVSE && !showEvse) continue; + + const label = sub.type === SUB_DEVICE_TYPE_EVSE ? "EV Charger" : sub.type === SUB_DEVICE_TYPE_BESS ? "Battery" : "Sub-device"; + const powerEid = findSubDevicePowerEntity(sub); + const powerState = powerEid ? hass.states[powerEid] : null; + const powerW = powerState ? parseFloat(powerState.state) || 0 : 0; + + const isBess = sub.type === SUB_DEVICE_TYPE_BESS; + const battLevelEid = isBess ? findBatteryLevelEntity(sub) : null; + const battSoeEid = isBess ? findBatterySoeEntity(sub) : null; + const battCapEid = isBess ? findBatteryCapacityEntity(sub) : null; + + const hideEids = new Set([powerEid, battLevelEid, battSoeEid, battCapEid].filter(Boolean)); + const entHTML = buildSubEntityHTML(sub, hass, config, hideEids); + const chartsHTML = buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid); + + subDevHTML += ` +
+
+ ${escapeHtml(label)} + ${escapeHtml(sub.name || "")} + ${powerEid ? `${formatPowerSigned(powerW)} ${formatPowerUnit(powerW)}` : ""} +
+ ${chartsHTML} + ${entHTML} +
+ `; + } + return subDevHTML; +} + +/** + * Build the HTML for the visible entities of a single sub-device. + * + * @param {object} sub - Sub-device object from topology + * @param {object} hass - Home Assistant object + * @param {object} config - Card configuration + * @param {Set} hideEids - Entity IDs to suppress (already shown elsewhere) + * @returns {string} HTML string + */ +export function buildSubEntityHTML(sub, hass, config, hideEids) { + const visibleEnts = config.visible_sub_entities || {}; + let entHTML = ""; + if (!sub.entities) return entHTML; + + for (const [entityId, info] of Object.entries(sub.entities)) { + if (hideEids.has(entityId)) continue; + if (visibleEnts[entityId] !== true) continue; + const state = hass.states[entityId]; + if (!state) continue; + let name = info.original_name || state.attributes.friendly_name || entityId; + const devName = sub.name || ""; + if (name.startsWith(devName + " ")) name = name.slice(devName.length + 1); + let displayValue; + if (hass.formatEntityState) { + displayValue = hass.formatEntityState(state); + } else { + displayValue = state.state; + const unit = state.attributes.unit_of_measurement || ""; + if (unit) displayValue += " " + unit; + } + const rawUnit = state.attributes.unit_of_measurement || ""; + if (rawUnit === "Wh") { + const wh = parseFloat(state.state); + if (!isNaN(wh)) displayValue = (wh / 1000).toFixed(1) + " kWh"; + } + entHTML += ` +
+ ${escapeHtml(name)}: + ${escapeHtml(displayValue)} +
+ `; + } + return entHTML; +} + +/** + * Build the chart container HTML for a sub-device. + * + * @param {string} devId - Sub-device key + * @param {object} sub - Sub-device object from topology (unused, reserved for future use) + * @param {boolean} isBess - Whether this sub-device is a battery + * @param {string|null} powerEid - Power entity ID, if available + * @param {string|null} battLevelEid - Battery level entity ID, if available + * @param {string|null} battSoeEid - Battery SoE entity ID, if available + * @returns {string} HTML string + */ +export function buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid) { + void sub; // reserved parameter; topology context may be needed in future + if (isBess) { + const bessCharts = [ + { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soc`, title: "SoC", available: !!battLevelEid }, + { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soe`, title: "SoE", available: !!battSoeEid }, + { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_power`, title: "Power", available: !!powerEid }, + ].filter(c => c.available); + + return ` +
+ ${bessCharts + .map( + c => ` +
+
${escapeHtml(c.title)}
+
+
+ ` + ) + .join("")} +
+ `; + } + if (powerEid) { + return `
`; + } + return ""; +} From ba2d750955018038cb8515adaa25fe6519ed6ec7 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:00:09 -0700 Subject: [PATCH 004/101] refactor: extract history loader to core module Move _loadHistory, _loadStatisticsHistory, _loadRawHistory, and _collectSubDeviceEntityIds out of SpanPanelCard into src/core/history-loader.js. Export loadHistory and collectSubDeviceEntityIds; internal helpers are module-private. Update _recordPowerHistory to use the imported collectSubDeviceEntityIds and remove now-unused imports. --- src/card/span-panel-card.js | 146 ++------------------------------- src/core/history-loader.js | 159 ++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 137 deletions(-) create mode 100644 src/core/history-loader.js diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 4e061ec..68382da 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -1,20 +1,12 @@ -import { - CHART_METRICS, - BESS_CHART_METRICS, - DEFAULT_CHART_METRIC, - LIVE_SAMPLE_INTERVAL_MS, - DEVICE_TYPE_PV, - RELAY_STATE_CLOSED, - SUB_DEVICE_TYPE_BESS, - SUB_DEVICE_KEY_PREFIX, -} from "../constants.js"; +import { CHART_METRICS, BESS_CHART_METRICS, DEFAULT_CHART_METRIC, LIVE_SAMPLE_INTERVAL_MS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; -import { getHistoryDurationMs, getMaxHistoryPoints, getMinGapMs, recordSample, deduplicateAndTrim } from "../helpers/history.js"; +import { getHistoryDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; import { getChartMetric, getCircuitChartEntity } from "../helpers/chart.js"; import { buildGridHTML } from "../core/grid-renderer.js"; import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; -import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity } from "../helpers/entity-finder.js"; +import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; +import { loadHistory, collectSubDeviceEntityIds } from "../core/history-loader.js"; import { updateChart } from "../chart/chart-update.js"; import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; import { CARD_STYLES } from "./card-styles.js"; @@ -144,123 +136,14 @@ export class SpanPanelCard extends HTMLElement { async _loadHistory() { if (this._historyLoaded || !this._topology || !this._hass) return; this._historyLoaded = true; - - const durationMs = this._durationMs; - const entityIds = []; - const uuidByEntity = new Map(); - - for (const [uuid, circuit] of Object.entries(this._topology.circuits)) { - const eid = getCircuitChartEntity(circuit, this._config); - if (eid) { - entityIds.push(eid); - uuidByEntity.set(eid, uuid); - } - } - - this._collectSubDeviceEntityIds(entityIds, uuidByEntity); - - if (entityIds.length === 0) return; - - const useStatistics = durationMs > 2 * 60 * 60 * 1000; - try { - if (useStatistics) { - await this._loadStatisticsHistory(entityIds, uuidByEntity, durationMs); - } else { - await this._loadRawHistory(entityIds, uuidByEntity, durationMs); - } + await loadHistory(this._hass, this._topology, this._config, this._powerHistory); this._updateDOM(); } catch (err) { console.warn("SPAN Panel: history fetch failed, charts will populate live", err); } } - async _loadStatisticsHistory(entityIds, uuidByEntity, durationMs) { - const startTime = new Date(Date.now() - durationMs).toISOString(); - const durationHours = durationMs / (60 * 60 * 1000); - const period = durationHours > 72 ? "hour" : "5minute"; - - const result = await this._hass.callWS({ - type: "recorder/statistics_during_period", - start_time: startTime, - statistic_ids: entityIds, - period, - types: ["mean"], - }); - - for (const [entityId, stats] of Object.entries(result)) { - const uuid = uuidByEntity.get(entityId); - if (!uuid || !stats) continue; - - const hist = []; - for (const entry of stats) { - const val = entry.mean; - if (val == null || !Number.isFinite(val)) continue; - const time = entry.start; - if (time > 0) hist.push({ time, value: val }); - } - - if (hist.length > 0) { - const existing = this._powerHistory.get(uuid) || []; - const merged = [...hist, ...existing]; - merged.sort((a, b) => a.time - b.time); - this._powerHistory.set(uuid, merged); - } - } - } - - async _loadRawHistory(entityIds, uuidByEntity, durationMs) { - const startTime = new Date(Date.now() - durationMs).toISOString(); - const result = await this._hass.callWS({ - type: "history/history_during_period", - start_time: startTime, - entity_ids: entityIds, - minimal_response: true, - significant_changes_only: true, - no_attributes: true, - }); - - const maxPoints = getMaxHistoryPoints(durationMs); - const minGapMs = getMinGapMs(durationMs); - for (const [entityId, states] of Object.entries(result)) { - const uuid = uuidByEntity.get(entityId); - if (!uuid || !states) continue; - - const hist = []; - for (const entry of states) { - const val = parseFloat(entry.s); - if (!Number.isFinite(val)) continue; - const tsSec = entry.lu || entry.lc || 0; - const time = tsSec * 1000; - if (time > 0) hist.push({ time, value: val }); - } - - if (hist.length > 0) { - const existing = this._powerHistory.get(uuid) || []; - const merged = [...hist, ...existing]; - this._powerHistory.set(uuid, deduplicateAndTrim(merged, maxPoints, minGapMs)); - } - } - } - - // Collect entity IDs for sub-devices into the provided arrays. - _collectSubDeviceEntityIds(entityIds, uuidByEntity) { - if (!this._topology.sub_devices) return; - for (const [devId, sub] of Object.entries(this._topology.sub_devices)) { - const eidMap = { power: findSubDevicePowerEntity(sub) }; - if (sub.type === SUB_DEVICE_TYPE_BESS) { - eidMap.soc = findBatteryLevelEntity(sub); - eidMap.soe = findBatterySoeEntity(sub); - } - for (const [role, eid] of Object.entries(eidMap)) { - if (eid) { - entityIds.push(eid); - uuidByEntity.set(eid, `${SUB_DEVICE_KEY_PREFIX}${devId}_${role}`); - } - } - } - } - // ── Record live power samples ────────────────────────────────────────────── _recordPowerHistory() { @@ -277,21 +160,10 @@ export class SpanPanelCard extends HTMLElement { recordSample(this._powerHistory, uuid, rawValue, now, cutoff, maxPoints); } - if (this._topology.sub_devices) { - for (const [devId, sub] of Object.entries(this._topology.sub_devices)) { - const eidMap = { power: findSubDevicePowerEntity(sub) }; - if (sub.type === SUB_DEVICE_TYPE_BESS) { - eidMap.soc = findBatteryLevelEntity(sub); - eidMap.soe = findBatterySoeEntity(sub); - } - for (const [role, entityId] of Object.entries(eidMap)) { - if (!entityId) continue; - const key = `${SUB_DEVICE_KEY_PREFIX}${devId}_${role}`; - const state = this._hass.states[entityId]; - const rawValue = state ? parseFloat(state.state) || 0 : 0; - recordSample(this._powerHistory, key, rawValue, now, cutoff, maxPoints); - } - } + for (const { entityId, key } of collectSubDeviceEntityIds(this._topology, this._hass)) { + const state = this._hass.states[entityId]; + const rawValue = state ? parseFloat(state.state) || 0 : 0; + recordSample(this._powerHistory, key, rawValue, now, cutoff, maxPoints); } } diff --git a/src/core/history-loader.js b/src/core/history-loader.js new file mode 100644 index 0000000..d41d6ae --- /dev/null +++ b/src/core/history-loader.js @@ -0,0 +1,159 @@ +import { getHistoryDurationMs, getMaxHistoryPoints, getMinGapMs, deduplicateAndTrim } from "../helpers/history.js"; +import { getCircuitChartEntity } from "../helpers/chart.js"; +import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity } from "../helpers/entity-finder.js"; +import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_KEY_PREFIX } from "../constants.js"; + +async function loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory) { + const startTime = new Date(Date.now() - durationMs).toISOString(); + const durationHours = durationMs / (60 * 60 * 1000); + const period = durationHours > 72 ? "hour" : "5minute"; + + const result = await hass.callWS({ + type: "recorder/statistics_during_period", + start_time: startTime, + statistic_ids: entityIds, + period, + types: ["mean"], + }); + + for (const [entityId, stats] of Object.entries(result)) { + const uuid = uuidByEntity.get(entityId); + if (!uuid || !stats) continue; + + const hist = []; + for (const entry of stats) { + const val = entry.mean; + if (val == null || !Number.isFinite(val)) continue; + const time = entry.start; + if (time > 0) hist.push({ time, value: val }); + } + + if (hist.length > 0) { + const existing = powerHistory.get(uuid) || []; + const merged = [...hist, ...existing]; + merged.sort((a, b) => a.time - b.time); + powerHistory.set(uuid, merged); + } + } +} + +async function loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory) { + const startTime = new Date(Date.now() - durationMs).toISOString(); + const result = await hass.callWS({ + type: "history/history_during_period", + start_time: startTime, + entity_ids: entityIds, + minimal_response: true, + significant_changes_only: true, + no_attributes: true, + }); + + const maxPoints = getMaxHistoryPoints(durationMs); + const minGapMs = getMinGapMs(durationMs); + for (const [entityId, states] of Object.entries(result)) { + const uuid = uuidByEntity.get(entityId); + if (!uuid || !states) continue; + + const hist = []; + for (const entry of states) { + const val = parseFloat(entry.s); + if (!Number.isFinite(val)) continue; + const tsSec = entry.lu || entry.lc || 0; + const time = tsSec * 1000; + if (time > 0) hist.push({ time, value: val }); + } + + if (hist.length > 0) { + const existing = powerHistory.get(uuid) || []; + const merged = [...hist, ...existing]; + powerHistory.set(uuid, deduplicateAndTrim(merged, maxPoints, minGapMs)); + } + } +} + +/** + * Collect entity IDs for sub-devices into the provided arrays. + * + * @param {object} topology + * @param {object} _hass - unused, kept for a consistent call signature + * @param {string[]} entityIds - mutated in place + * @param {Map} uuidByEntity - mutated in place + */ +function _collectSubDeviceEntityIdsInto(topology, entityIds, uuidByEntity) { + if (!topology.sub_devices) return; + for (const [devId, sub] of Object.entries(topology.sub_devices)) { + const eidMap = { power: findSubDevicePowerEntity(sub) }; + if (sub.type === SUB_DEVICE_TYPE_BESS) { + eidMap.soc = findBatteryLevelEntity(sub); + eidMap.soe = findBatterySoeEntity(sub); + } + for (const [role, eid] of Object.entries(eidMap)) { + if (eid) { + entityIds.push(eid); + uuidByEntity.set(eid, `${SUB_DEVICE_KEY_PREFIX}${devId}_${role}`); + } + } + } +} + +/** + * Build the entity ID list for all sub-devices. + * Returns an array of { entityId, key } pairs so callers can record live samples. + * + * @param {object} topology + * @param {object} _hass - reserved for future use + * @returns {{ entityId: string, key: string }[]} + */ +export function collectSubDeviceEntityIds(topology, _hass) { + if (!topology.sub_devices) return []; + const results = []; + for (const [devId, sub] of Object.entries(topology.sub_devices)) { + const eidMap = { power: findSubDevicePowerEntity(sub) }; + if (sub.type === SUB_DEVICE_TYPE_BESS) { + eidMap.soc = findBatteryLevelEntity(sub); + eidMap.soe = findBatterySoeEntity(sub); + } + for (const [role, eid] of Object.entries(eidMap)) { + if (eid) { + results.push({ entityId: eid, key: `${SUB_DEVICE_KEY_PREFIX}${devId}_${role}` }); + } + } + } + return results; +} + +/** + * Load historical power data from HA recorder into the powerHistory Map. + * + * @param {object} hass + * @param {object} topology + * @param {object} config + * @param {Map} powerHistory - mutated in place + */ +export async function loadHistory(hass, topology, config, powerHistory) { + if (!topology || !hass) return; + + const durationMs = getHistoryDurationMs(config); + const entityIds = []; + const uuidByEntity = new Map(); + + for (const [uuid, circuit] of Object.entries(topology.circuits)) { + const eid = getCircuitChartEntity(circuit, config); + if (eid) { + entityIds.push(eid); + uuidByEntity.set(eid, uuid); + } + } + + _collectSubDeviceEntityIdsInto(topology, entityIds, uuidByEntity); + + if (entityIds.length === 0) return; + + const useStatistics = durationMs > 2 * 60 * 60 * 1000; + + if (useStatistics) { + await loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory); + } else { + await loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory); + } +} From 8d5fd348c3fe5d4261eda381f7f118b90733dc53 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:04:50 -0700 Subject: [PATCH 005/101] refactor: extract DOM updater to core module Move _updateDOM, _updateSubDeviceDOM, and _findPanelEntity out of SpanPanelCard into src/core/dom-updater.js as pure functions updateCircuitDOM, updateSubDeviceDOM, and a module-private _findPanelEntity. The card delegates to these via a thin _updateDOM wrapper. --- src/card/span-panel-card.js | 147 ++--------------------------------- src/core/dom-updater.js | 151 ++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 141 deletions(-) create mode 100644 src/core/dom-updater.js diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 68382da..0a23737 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -1,13 +1,11 @@ -import { CHART_METRICS, BESS_CHART_METRICS, DEFAULT_CHART_METRIC, LIVE_SAMPLE_INTERVAL_MS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../constants.js"; +import { DEFAULT_CHART_METRIC, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; -import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; import { getHistoryDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; -import { getChartMetric, getCircuitChartEntity } from "../helpers/chart.js"; +import { getCircuitChartEntity } from "../helpers/chart.js"; import { buildGridHTML } from "../core/grid-renderer.js"; import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; -import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; import { loadHistory, collectSubDeviceEntityIds } from "../core/history-loader.js"; -import { updateChart } from "../chart/chart-update.js"; +import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; import { CARD_STYLES } from "./card-styles.js"; @@ -177,142 +175,9 @@ export class SpanPanelCard extends HTMLElement { // ── DOM updates (incremental) ────────────────────────────────────────────── _updateDOM() { - const root = this.shadowRoot; - if (!root || !this._topology || !this._hass) return; - - const hass = this._hass; - const topo = this._topology; - const durationMs = this._durationMs; - - let totalConsumption = 0; - let solarProduction = 0; - - for (const [, circuit] of Object.entries(topo.circuits)) { - const entityId = circuit.entities?.power; - if (!entityId) continue; - const state = hass.states[entityId]; - const power = state ? parseFloat(state.state) || 0 : 0; - if (circuit.device_type === DEVICE_TYPE_PV) { - solarProduction += Math.abs(power); - } else { - totalConsumption += Math.abs(power); - } - } - - const panelPowerEntity = this._findPanelEntity("current_power"); - if (panelPowerEntity) { - const state = hass.states[panelPowerEntity]; - if (state) totalConsumption = Math.abs(parseFloat(state.state) || 0); - } - - const consumptionEl = root.querySelector(".stat-consumption .stat-value"); - if (consumptionEl) consumptionEl.textContent = formatKw(totalConsumption); - - const currentEl = root.querySelector(".stat-current .stat-value"); - if (currentEl) { - const panelPowerEid = this._findPanelEntity("current_power"); - const panelPowerState = panelPowerEid ? hass.states[panelPowerEid] : null; - const amperage = panelPowerState ? parseFloat(panelPowerState.attributes?.amperage) : NaN; - currentEl.textContent = Number.isFinite(amperage) ? amperage.toFixed(1) : "--"; - } - const solarEl = root.querySelector(".stat-solar .stat-value"); - if (solarEl) solarEl.textContent = solarProduction > 0 ? formatKw(solarProduction) : "--"; - - const chartMetric = getChartMetric(this._config); - const showCurrent = chartMetric.entityRole === "current"; - - for (const [uuid, circuit] of Object.entries(topo.circuits)) { - const slot = root.querySelector(`[data-uuid="${uuid}"]`); - if (!slot) continue; - - const entityId = circuit.entities?.power; - const state = entityId ? hass.states[entityId] : null; - const powerW = state ? parseFloat(state.state) || 0 : 0; - const isProducer = circuit.device_type === DEVICE_TYPE_PV || powerW < 0; - - const switchEntityId = circuit.entities?.switch; - const switchState = switchEntityId ? hass.states[switchEntityId] : null; - const isOn = switchState ? switchState.state === "on" : (state?.attributes?.relay_state || circuit.relay_state) === RELAY_STATE_CLOSED; - - const powerVal = slot.querySelector(".power-value"); - if (powerVal) { - if (showCurrent) { - const currentEid = circuit.entities?.current; - const currentState = currentEid ? hass.states[currentEid] : null; - const amps = currentState ? parseFloat(currentState.state) || 0 : 0; - powerVal.innerHTML = `${chartMetric.format(amps)}A`; - } else { - powerVal.innerHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; - } - } - - const toggle = slot.querySelector(".toggle-pill"); - if (toggle) { - toggle.className = `toggle-pill ${isOn ? "toggle-on" : "toggle-off"}`; - const label = toggle.querySelector(".toggle-label"); - if (label) label.textContent = isOn ? "On" : "Off"; - } - - slot.classList.toggle("circuit-off", !isOn); - slot.classList.toggle("circuit-producer", isProducer); - - const chartContainer = slot.querySelector(".chart-container"); - if (chartContainer) { - const history = this._powerHistory.get(uuid) || []; - const h = slot.classList.contains("circuit-col-span") ? 200 : 100; - updateChart(chartContainer, hass, history, durationMs, chartMetric, isProducer, h, circuit.breaker_rating_a); - } - } - - this._updateSubDeviceDOM(root, hass, topo, durationMs); - } - - _updateSubDeviceDOM(root, hass, topo, durationMs) { - if (!topo.sub_devices) return; - for (const [devId, sub] of Object.entries(topo.sub_devices)) { - const section = root.querySelector(`[data-subdev="${devId}"]`); - if (!section) continue; - - const powerEid = findSubDevicePowerEntity(sub); - if (powerEid) { - const state = hass.states[powerEid]; - const powerW = state ? parseFloat(state.state) || 0 : 0; - const powerEl = section.querySelector(".sub-power-value"); - if (powerEl) { - powerEl.innerHTML = `${formatPowerSigned(powerW)} ${formatPowerUnit(powerW)}`; - } - } - - const chartContainers = section.querySelectorAll("[data-chart-key]"); - for (const cc of chartContainers) { - const chartKey = cc.dataset.chartKey; - const history = this._powerHistory.get(chartKey) || []; - let metric = CHART_METRICS.power; - if (chartKey.endsWith("_soc")) metric = BESS_CHART_METRICS.soc; - else if (chartKey.endsWith("_soe")) metric = BESS_CHART_METRICS.soe; - const isBessCol = !!cc.closest(".bess-chart-col"); - updateChart(cc, hass, history, durationMs, metric, false, isBessCol ? 120 : 150); - } - - for (const entityId of Object.keys(sub.entities || {})) { - const valEl = section.querySelector(`[data-eid="${entityId}"]`); - if (!valEl) continue; - const state = hass.states[entityId]; - if (state) { - valEl.textContent = `${state.state}${state.attributes.unit_of_measurement ? " " + state.attributes.unit_of_measurement : ""}`; - } - } - } - } - - _findPanelEntity(suffix) { - if (!this._hass) return null; - for (const entityId of Object.keys(this._hass.states)) { - if (entityId.startsWith("sensor.span_panel_") && entityId.endsWith(`_${suffix}`)) { - return entityId; - } - } - return null; + const config = { ...this._config, _durationMs: this._durationMs }; + updateCircuitDOM(this.shadowRoot, this._hass, this._topology, config, this._powerHistory); + updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, config, this._powerHistory); } // ── Toggle click handler ─────────────────────────────────────────────────── diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js new file mode 100644 index 0000000..1c2d75a --- /dev/null +++ b/src/core/dom-updater.js @@ -0,0 +1,151 @@ +import { BESS_CHART_METRICS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../constants.js"; +import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; +import { getChartMetric } from "../helpers/chart.js"; +import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; +import { updateChart } from "../chart/chart-update.js"; + +// ── Private helper ───────────────────────────────────────────────────────── + +function _findPanelEntity(hass, _topology, suffix) { + if (!hass) return null; + for (const entityId of Object.keys(hass.states)) { + if (entityId.startsWith("sensor.span_panel_") && entityId.endsWith(`_${suffix}`)) { + return entityId; + } + } + return null; +} + +// ── Header stats ─────────────────────────────────────────────────────────── + +function _updateHeaderStats(root, hass, topology, totalConsumption, solarProduction) { + const panelPowerEntity = _findPanelEntity(hass, topology, "current_power"); + if (panelPowerEntity) { + const state = hass.states[panelPowerEntity]; + if (state) totalConsumption = Math.abs(parseFloat(state.state) || 0); + } + + const consumptionEl = root.querySelector(".stat-consumption .stat-value"); + if (consumptionEl) consumptionEl.textContent = formatKw(totalConsumption); + + const currentEl = root.querySelector(".stat-current .stat-value"); + if (currentEl) { + const panelPowerEid = _findPanelEntity(hass, topology, "current_power"); + const panelPowerState = panelPowerEid ? hass.states[panelPowerEid] : null; + const amperage = panelPowerState ? parseFloat(panelPowerState.attributes?.amperage) : NaN; + currentEl.textContent = Number.isFinite(amperage) ? amperage.toFixed(1) : "--"; + } + + const solarEl = root.querySelector(".stat-solar .stat-value"); + if (solarEl) solarEl.textContent = solarProduction > 0 ? formatKw(solarProduction) : "--"; +} + +// ── Exported updaters ────────────────────────────────────────────────────── + +export function updateCircuitDOM(root, hass, topology, config, powerHistory) { + if (!root || !topology || !hass) return; + + const durationMs = config._durationMs; + let totalConsumption = 0; + let solarProduction = 0; + + for (const [, circuit] of Object.entries(topology.circuits)) { + const entityId = circuit.entities?.power; + if (!entityId) continue; + const state = hass.states[entityId]; + const power = state ? parseFloat(state.state) || 0 : 0; + if (circuit.device_type === DEVICE_TYPE_PV) { + solarProduction += Math.abs(power); + } else { + totalConsumption += Math.abs(power); + } + } + + _updateHeaderStats(root, hass, topology, totalConsumption, solarProduction); + + const chartMetric = getChartMetric(config); + const showCurrent = chartMetric.entityRole === "current"; + + for (const [uuid, circuit] of Object.entries(topology.circuits)) { + const slot = root.querySelector(`[data-uuid="${uuid}"]`); + if (!slot) continue; + + const entityId = circuit.entities?.power; + const state = entityId ? hass.states[entityId] : null; + const powerW = state ? parseFloat(state.state) || 0 : 0; + const isProducer = circuit.device_type === DEVICE_TYPE_PV || powerW < 0; + + const switchEntityId = circuit.entities?.switch; + const switchState = switchEntityId ? hass.states[switchEntityId] : null; + const isOn = switchState ? switchState.state === "on" : (state?.attributes?.relay_state || circuit.relay_state) === RELAY_STATE_CLOSED; + + const powerVal = slot.querySelector(".power-value"); + if (powerVal) { + if (showCurrent) { + const currentEid = circuit.entities?.current; + const currentState = currentEid ? hass.states[currentEid] : null; + const amps = currentState ? parseFloat(currentState.state) || 0 : 0; + powerVal.innerHTML = `${chartMetric.format(amps)}A`; + } else { + powerVal.innerHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; + } + } + + const toggle = slot.querySelector(".toggle-pill"); + if (toggle) { + toggle.className = `toggle-pill ${isOn ? "toggle-on" : "toggle-off"}`; + const label = toggle.querySelector(".toggle-label"); + if (label) label.textContent = isOn ? "On" : "Off"; + } + + slot.classList.toggle("circuit-off", !isOn); + slot.classList.toggle("circuit-producer", isProducer); + + const chartContainer = slot.querySelector(".chart-container"); + if (chartContainer) { + const history = powerHistory.get(uuid) || []; + const h = slot.classList.contains("circuit-col-span") ? 200 : 100; + updateChart(chartContainer, hass, history, durationMs, chartMetric, isProducer, h, circuit.breaker_rating_a); + } + } +} + +export function updateSubDeviceDOM(root, hass, topology, config, powerHistory) { + if (!topology.sub_devices) return; + const durationMs = config._durationMs; + + for (const [devId, sub] of Object.entries(topology.sub_devices)) { + const section = root.querySelector(`[data-subdev="${devId}"]`); + if (!section) continue; + + const powerEid = findSubDevicePowerEntity(sub); + if (powerEid) { + const state = hass.states[powerEid]; + const powerW = state ? parseFloat(state.state) || 0 : 0; + const powerEl = section.querySelector(".sub-power-value"); + if (powerEl) { + powerEl.innerHTML = `${formatPowerSigned(powerW)} ${formatPowerUnit(powerW)}`; + } + } + + const chartContainers = section.querySelectorAll("[data-chart-key]"); + for (const cc of chartContainers) { + const chartKey = cc.dataset.chartKey; + const history = powerHistory.get(chartKey) || []; + let metric = BESS_CHART_METRICS.power; + if (chartKey.endsWith("_soc")) metric = BESS_CHART_METRICS.soc; + else if (chartKey.endsWith("_soe")) metric = BESS_CHART_METRICS.soe; + const isBessCol = !!cc.closest(".bess-chart-col"); + updateChart(cc, hass, history, durationMs, metric, false, isBessCol ? 120 : 150); + } + + for (const entityId of Object.keys(sub.entities || {})) { + const valEl = section.querySelector(`[data-eid="${entityId}"]`); + if (!valEl) continue; + const state = hass.states[entityId]; + if (state) { + valEl.textContent = `${state.state}${state.attributes.unit_of_measurement ? " " + state.attributes.unit_of_measurement : ""}`; + } + } + } +} From 8421c5894951232c9c51418412cbba1077aa1635 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:13:16 -0700 Subject: [PATCH 006/101] refactor: fix code review issues in core module extraction --- src/card/span-panel-card.js | 7 +++---- src/core/dom-updater.js | 5 +++-- src/core/history-loader.js | 20 ++++---------------- src/core/sub-device-renderer.js | 3 +-- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 0a23737..5f66750 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -158,7 +158,7 @@ export class SpanPanelCard extends HTMLElement { recordSample(this._powerHistory, uuid, rawValue, now, cutoff, maxPoints); } - for (const { entityId, key } of collectSubDeviceEntityIds(this._topology, this._hass)) { + for (const { entityId, key } of collectSubDeviceEntityIds(this._topology)) { const state = this._hass.states[entityId]; const rawValue = state ? parseFloat(state.state) || 0 : 0; recordSample(this._powerHistory, key, rawValue, now, cutoff, maxPoints); @@ -175,9 +175,8 @@ export class SpanPanelCard extends HTMLElement { // ── DOM updates (incremental) ────────────────────────────────────────────── _updateDOM() { - const config = { ...this._config, _durationMs: this._durationMs }; - updateCircuitDOM(this.shadowRoot, this._hass, this._topology, config, this._powerHistory); - updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, config, this._powerHistory); + updateCircuitDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory); + updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory); } // ── Toggle click handler ─────────────────────────────────────────────────── diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index 1c2d75a..d38a9e4 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -2,6 +2,7 @@ import { BESS_CHART_METRICS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../const import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; import { getChartMetric } from "../helpers/chart.js"; import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; +import { getHistoryDurationMs } from "../helpers/history.js"; import { updateChart } from "../chart/chart-update.js"; // ── Private helper ───────────────────────────────────────────────────────── @@ -45,7 +46,7 @@ function _updateHeaderStats(root, hass, topology, totalConsumption, solarProduct export function updateCircuitDOM(root, hass, topology, config, powerHistory) { if (!root || !topology || !hass) return; - const durationMs = config._durationMs; + const durationMs = getHistoryDurationMs(config); let totalConsumption = 0; let solarProduction = 0; @@ -112,7 +113,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory) { export function updateSubDeviceDOM(root, hass, topology, config, powerHistory) { if (!topology.sub_devices) return; - const durationMs = config._durationMs; + const durationMs = getHistoryDurationMs(config); for (const [devId, sub] of Object.entries(topology.sub_devices)) { const section = root.querySelector(`[data-subdev="${devId}"]`); diff --git a/src/core/history-loader.js b/src/core/history-loader.js index d41d6ae..0045a00 100644 --- a/src/core/history-loader.js +++ b/src/core/history-loader.js @@ -75,24 +75,13 @@ async function loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHi * Collect entity IDs for sub-devices into the provided arrays. * * @param {object} topology - * @param {object} _hass - unused, kept for a consistent call signature * @param {string[]} entityIds - mutated in place * @param {Map} uuidByEntity - mutated in place */ function _collectSubDeviceEntityIdsInto(topology, entityIds, uuidByEntity) { - if (!topology.sub_devices) return; - for (const [devId, sub] of Object.entries(topology.sub_devices)) { - const eidMap = { power: findSubDevicePowerEntity(sub) }; - if (sub.type === SUB_DEVICE_TYPE_BESS) { - eidMap.soc = findBatteryLevelEntity(sub); - eidMap.soe = findBatterySoeEntity(sub); - } - for (const [role, eid] of Object.entries(eidMap)) { - if (eid) { - entityIds.push(eid); - uuidByEntity.set(eid, `${SUB_DEVICE_KEY_PREFIX}${devId}_${role}`); - } - } + for (const { entityId, key } of collectSubDeviceEntityIds(topology)) { + entityIds.push(entityId); + uuidByEntity.set(entityId, key); } } @@ -101,10 +90,9 @@ function _collectSubDeviceEntityIdsInto(topology, entityIds, uuidByEntity) { * Returns an array of { entityId, key } pairs so callers can record live samples. * * @param {object} topology - * @param {object} _hass - reserved for future use * @returns {{ entityId: string, key: string }[]} */ -export function collectSubDeviceEntityIds(topology, _hass) { +export function collectSubDeviceEntityIds(topology) { if (!topology.sub_devices) return []; const results = []; for (const [devId, sub] of Object.entries(topology.sub_devices)) { diff --git a/src/core/sub-device-renderer.js b/src/core/sub-device-renderer.js index 85b432c..8be46a5 100644 --- a/src/core/sub-device-renderer.js +++ b/src/core/sub-device-renderer.js @@ -108,8 +108,7 @@ export function buildSubEntityHTML(sub, hass, config, hideEids) { * @param {string|null} battSoeEid - Battery SoE entity ID, if available * @returns {string} HTML string */ -export function buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid) { - void sub; // reserved parameter; topology context may be needed in future +export function buildSubDeviceChartsHTML(devId, _sub, isBess, powerEid, battLevelEid, battSoeEid) { if (isBess) { const bessCharts = [ { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soc`, title: "SoC", available: !!battLevelEid }, From df8f877c288a705b69f78bc54b4e567dd0c9299f Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:16:26 -0700 Subject: [PATCH 007/101] refactor: extract header renderer to core module with new stats Moves panel header HTML out of _render() into buildHeaderHTML() in src/core/header-renderer.js. New header adds Grid state, Upstream, Downstream, and A/W unit toggle button with gear icon for panel monitoring config. Adds supporting CSS for gear icon and unit toggle. --- src/card/card-styles.js | 32 +++++++++++++++ src/card/span-panel-card.js | 33 ++-------------- src/core/header-renderer.js | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 src/core/header-renderer.js diff --git a/src/card/card-styles.js b/src/card/card-styles.js index 024b638..c039436 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -55,6 +55,38 @@ export const CARD_STYLES = ` .header-right { display: flex; gap: 20px; align-items: center; padding-top: 8px; } .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); } + .panel-gear { + background: none; + border: none; + cursor: pointer; + color: var(--secondary-text-color); + opacity: 0.6; + padding: 4px; + margin-left: 8px; + vertical-align: middle; + } + .panel-gear:hover { opacity: 1; } + .unit-toggle { + display: inline-flex; + background: var(--secondary-background-color, #333); + border-radius: 6px; + overflow: hidden; + margin-left: 8px; + } + .unit-btn { + padding: 4px 10px; + border: none; + background: none; + color: var(--secondary-text-color); + font-size: 0.75em; + font-weight: 600; + cursor: pointer; + } + .unit-btn.unit-active { + background: var(--primary-color, #4dd9af); + color: var(--text-primary-color, #000); + } + .panel-grid { display: grid; grid-template-columns: 28px 1fr 1fr 28px; diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 5f66750..e7a3e71 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -2,6 +2,7 @@ import { DEFAULT_CHART_METRIC, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; import { getHistoryDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; +import { buildHeaderHTML } from "../core/header-renderer.js"; import { buildGridHTML } from "../core/grid-renderer.js"; import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; import { loadHistory, collectSubDeviceEntityIds } from "../core/history-loader.js"; @@ -222,9 +223,9 @@ export class SpanPanelCard extends HTMLElement { const topo = this._topology; const totalRows = Math.ceil(this._panelSize / 2); - const panelName = escapeHtml(topo.device_name || "SPAN Panel"); const durationMs = this._durationMs; + const headerHTML = buildHeaderHTML(topo, this._config); const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config); const subDevHTML = buildSubDevicesHTML(topo, hass, this._config, durationMs); @@ -234,35 +235,7 @@ export class SpanPanelCard extends HTMLElement { this.shadowRoot.innerHTML = ` -
-
-
-

${panelName}

- ${escapeHtml(topo.serial || "")} -
-
-
- Panel consumption -
0kW
-
-
- Total current -
--A
-
-
- Solar production -
--kW
-
-
- Battery charge/discharge -
-
-
-
-
- Firmware: ${escapeHtml(topo.firmware || "")} -
-
+ ${headerHTML} ${ this._config.show_panel !== false ? ` diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js new file mode 100644 index 0000000..3594a22 --- /dev/null +++ b/src/core/header-renderer.js @@ -0,0 +1,78 @@ +import { escapeHtml } from "../helpers/sanitize.js"; + +/** + * Build the panel header HTML with stats, gear icon, and A/W toggle. + * @param {object} topology - Panel topology from WebSocket + * @param {object} config - Card/panel configuration + * @returns {string} HTML string + */ +export function buildHeaderHTML(topology, config) { + const panelName = escapeHtml(topology.device_name || "SPAN Panel"); + const serial = escapeHtml(topology.serial || ""); + const firmware = escapeHtml(topology.firmware || ""); + const isAmpsMode = (config.chart_metric || "power") === "current"; + + return ` +
+
+
+

${panelName}

+ ${serial} + +
+
+
+ Site +
+ 0 + ${isAmpsMode ? "A" : "kW"} +
+
+
+ Grid +
+ -- +
+
+
+ Upstream +
+ -- + ${isAmpsMode ? "A" : "kW"} +
+
+
+ Downstream +
+ -- + ${isAmpsMode ? "A" : "kW"} +
+
+
+ Solar +
+ -- + ${isAmpsMode ? "A" : "kW"} +
+
+
+ Battery +
+ + % +
+
+
+
+
+ ${firmware} +
+ + +
+
+
+ `; +} From f44bb2431fa4b56d422fccf5914972cb532f2b07 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:18:48 -0700 Subject: [PATCH 008/101] feat: add monitoring status fetcher and helpers --- src/core/monitoring-status.js | 107 ++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/core/monitoring-status.js diff --git a/src/core/monitoring-status.js b/src/core/monitoring-status.js new file mode 100644 index 0000000..303d9bf --- /dev/null +++ b/src/core/monitoring-status.js @@ -0,0 +1,107 @@ +// src/core/monitoring-status.js +import { INTEGRATION_DOMAIN } from "../constants.js"; + +const MONITORING_POLL_INTERVAL_MS = 30_000; + +/** + * Caches monitoring status fetched via the get_monitoring_status service. + * Re-fetches at most every 30 seconds. + */ +export class MonitoringStatusCache { + constructor() { + this._status = null; + this._lastFetch = 0; + this._fetching = false; + } + + /** + * Fetch monitoring status, returning cached data if recent. + * @param {object} hass - Home Assistant instance + * @returns {Promise} Monitoring status or null + */ + async fetch(hass) { + const now = Date.now(); + if (this._fetching) return this._status; + if (this._status && now - this._lastFetch < MONITORING_POLL_INTERVAL_MS) { + return this._status; + } + + this._fetching = true; + try { + const resp = await hass.callService(INTEGRATION_DOMAIN, "get_monitoring_status", {}, undefined, true); + this._status = resp?.response || null; + this._lastFetch = now; + } catch { + this._status = null; + } finally { + this._fetching = false; + } + return this._status; + } + + /** @returns {object|null} Last fetched status */ + get status() { + return this._status; + } + + /** Clear cached status (e.g., on config change). */ + clear() { + this._status = null; + this._lastFetch = 0; + } +} + +/** + * Get monitoring info for a specific circuit entity. + * @param {object|null} status - Full monitoring status + * @param {string} entityId - Circuit entity ID + * @returns {object|null} Circuit monitoring info or null + */ +export function getCircuitMonitoringInfo(status, entityId) { + if (!status?.circuits) return null; + return status.circuits[entityId] || null; +} + +/** + * Get monitoring info for a mains leg entity. + * @param {object|null} status - Full monitoring status + * @param {string} entityId - Mains entity ID + * @returns {object|null} Mains monitoring info or null + */ +export function getMainsMonitoringInfo(status, entityId) { + if (!status?.mains) return null; + return status.mains[entityId] || null; +} + +/** + * Check if a monitored point has custom (non-global) overrides. + * @param {object|null} monitoringInfo - Per-circuit monitoring info + * @returns {boolean} + */ +export function hasCustomOverrides(monitoringInfo) { + if (!monitoringInfo) return false; + return monitoringInfo.continuous_threshold_pct !== undefined; +} + +/** + * Get CSS class for utilization level. + * @param {object|null} monitoringInfo - Per-circuit monitoring info + * @returns {string} CSS class name or empty string + */ +export function getUtilizationClass(monitoringInfo) { + if (!monitoringInfo?.utilization_pct) return ""; + const pct = monitoringInfo.utilization_pct; + if (pct >= 100) return "utilization-alert"; + if (pct >= 80) return "utilization-warning"; + return "utilization-normal"; +} + +/** + * Check if a circuit currently has an active alert. + * @param {object|null} monitoringInfo - Per-circuit monitoring info + * @returns {boolean} + */ +export function isAlertActive(monitoringInfo) { + if (!monitoringInfo) return false; + return monitoringInfo.over_threshold_since != null; +} From 27f9f2c4c8b1c6df547f94cbfc8c918181421eb6 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:23:46 -0700 Subject: [PATCH 009/101] feat: add shedding icons, monitoring indicators, and gear icons to circuit cells --- src/card/card-styles.js | 33 ++++++++++++++++++++ src/card/span-panel-card.js | 2 +- src/constants.js | 16 ++++++++++ src/core/grid-renderer.js | 61 ++++++++++++++++++++++++++++++++----- 4 files changed, 104 insertions(+), 8 deletions(-) diff --git a/src/card/card-styles.js b/src/card/card-styles.js index c039436..087a27a 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -210,6 +210,39 @@ export const CARD_STYLES = ` order: -1; } + .circuit-status { + display: flex; + align-items: center; + gap: 4px; + margin-top: 4px; + padding: 0 4px; + } + .shedding-icon { opacity: 0.8; cursor: default; } + .gear-icon { + background: none; + border: none; + cursor: pointer; + padding: 2px; + opacity: 0.6; + transition: opacity 0.2s; + margin-left: auto; + } + .gear-icon:hover { opacity: 1; } + .utilization { + font-size: 0.75em; + font-weight: 600; + } + .utilization-normal { color: #4caf50; } + .utilization-warning { color: #ff9800; } + .utilization-alert { color: #f44336; } + .circuit-alert { + border-color: #f44336 !important; + box-shadow: 0 0 8px rgba(244, 67, 54, 0.3); + } + .circuit-custom-monitoring { + border-left: 3px solid #ff9800; + } + .chart-container { width: 100%; margin-top: 4px; diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index e7a3e71..33e5c06 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -226,7 +226,7 @@ export class SpanPanelCard extends HTMLElement { const durationMs = this._durationMs; const headerHTML = buildHeaderHTML(topo, this._config); - const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config); + const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config, null); const subDevHTML = buildSubDevicesHTML(topo, hass, this._config, durationMs); // Remove previous listener before replacing DOM diff --git a/src/constants.js b/src/constants.js index 7384239..1b5c5a4 100644 --- a/src/constants.js +++ b/src/constants.js @@ -49,3 +49,19 @@ export const BESS_CHART_METRICS = { }, power: CHART_METRICS.power, }; + +// ── Shedding priority ────────────────────────────────────────────────────── + +export const SHEDDING_PRIORITIES = { + never: { icon: "mdi:shield-check", color: "#4caf50", label: "Never" }, + soc_threshold: { icon: "mdi:battery-alert-variant-outline", color: "#9c27b0", label: "SoC Threshold" }, + off_grid: { icon: "mdi:transmission-tower", color: "#ff9800", label: "Off-Grid" }, + unknown: { icon: "mdi:help-circle-outline", color: "#888", label: "Unknown" }, +}; + +export const MONITORING_COLORS = { + normal: "#4caf50", + warning: "#ff9800", + alert: "#f44336", + custom: "#ff9800", +}; diff --git a/src/core/grid-renderer.js b/src/core/grid-renderer.js index ebe5920..fea5f2c 100644 --- a/src/core/grid-renderer.js +++ b/src/core/grid-renderer.js @@ -2,7 +2,8 @@ import { escapeHtml } from "../helpers/sanitize.js"; import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; import { tabToRow, tabToCol, classifyDualTab } from "../helpers/layout.js"; import { getChartMetric } from "../helpers/chart.js"; -import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../constants.js"; +import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES, MONITORING_COLORS } from "../constants.js"; +import { getCircuitMonitoringInfo, hasCustomOverrides, getUtilizationClass, isAlertActive } from "./monitoring-status.js"; /** * Build the full grid HTML for the panel breaker grid. @@ -14,7 +15,7 @@ import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../constants.js"; * @param {object} config - Card configuration object. * @returns {string} HTML string for the grid. */ -export function buildGridHTML(topology, totalRows, durationMs, hass, config) { +export function buildGridHTML(topology, totalRows, durationMs, hass, config, monitoringStatus) { const tabMap = new Map(); const occupiedTabs = new Set(); @@ -41,6 +42,14 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config) { } } + function lookupMonitoring(entry) { + const circuitEntityId = entry.circuit.entities?.current || entry.circuit.entities?.power; + const monInfo = monitoringStatus ? getCircuitMonitoringInfo(monitoringStatus, circuitEntityId) : null; + const selectEid = entry.circuit.entities?.select; + const sheddingPriority = selectEid && hass.states[selectEid] ? hass.states[selectEid].state : "unknown"; + return { monInfo, sheddingPriority }; + } + let gridHTML = ""; for (let row = 1; row <= totalRows; row++) { const leftTab = row * 2 - 1; @@ -51,14 +60,16 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config) { gridHTML += `
${leftTab}
`; if (leftEntry && leftEntry.layout === "row-span") { - gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 4", "row-span", durationMs, hass, config); + const { monInfo, sheddingPriority } = lookupMonitoring(leftEntry); + gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 4", "row-span", durationMs, hass, config, monInfo, sheddingPriority); gridHTML += `
${rightTab}
`; continue; } if (!rowsToSkipLeft.has(row)) { if (leftEntry && (leftEntry.layout === "col-span" || leftEntry.layout === "single")) { - gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2", leftEntry.layout, durationMs, hass, config); + const { monInfo, sheddingPriority } = lookupMonitoring(leftEntry); + gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2", leftEntry.layout, durationMs, hass, config, monInfo, sheddingPriority); } else if (!occupiedTabs.has(leftTab)) { gridHTML += renderEmptySlot(row, "2"); } @@ -66,7 +77,8 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config) { if (!rowsToSkipRight.has(row)) { if (rightEntry && (rightEntry.layout === "col-span" || rightEntry.layout === "single")) { - gridHTML += renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "3", rightEntry.layout, durationMs, hass, config); + const { monInfo, sheddingPriority } = lookupMonitoring(rightEntry); + gridHTML += renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "3", rightEntry.layout, durationMs, hass, config, monInfo, sheddingPriority); } else if (!occupiedTabs.has(rightTab)) { gridHTML += renderEmptySlot(row, "3"); } @@ -90,7 +102,7 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config) { * @param {object} config - Card configuration object. * @returns {string} HTML string for the circuit slot. */ -export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, hass, config) { +export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, hass, config, monitoringInfo, sheddingPriority) { const entityId = circuit.entities?.power; const state = entityId ? hass.states[entityId] : null; const powerW = state ? parseFloat(state.state) || 0 : 0; @@ -116,11 +128,41 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, valueHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; } + // Shedding icon + const priority = sheddingPriority || "unknown"; + const shedInfo = SHEDDING_PRIORITIES[priority] || SHEDDING_PRIORITIES.unknown; + const sheddingHTML = ``; + + // Gear icon + const hasOverridesFlag = monitoringInfo && hasCustomOverrides(monitoringInfo); + const gearColor = hasOverridesFlag ? MONITORING_COLORS.custom : "#555"; + const gearHTML = ``; + + // Utilization (shown when monitoring is active) + let utilizationHTML = ""; + if (monitoringInfo?.utilization_pct != null) { + const pct = monitoringInfo.utilization_pct; + const utilClass = getUtilizationClass(monitoringInfo); + utilizationHTML = `${Math.round(pct)}%`; + } + + // Alert and custom monitoring classes + const alertActive = isAlertActive(monitoringInfo); + const alertClass = alertActive ? "circuit-alert" : ""; + const customClass = hasOverridesFlag ? "circuit-custom-monitoring" : ""; + const rowSpan = layout === "col-span" ? `${row} / span 2` : `${row}`; const layoutClass = layout === "row-span" ? "circuit-row-span" : layout === "col-span" ? "circuit-col-span" : ""; return ` -
@@ -144,6 +186,11 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, }
+
+ ${sheddingHTML} + ${utilizationHTML} + ${gearHTML} +
`; From bfb9104dcbcadda4fc29f2949b1273b51b73247b Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:28:39 -0700 Subject: [PATCH 010/101] feat: add A/W toggle switching all values and chart axes Add delegated click handler for .unit-btn buttons that updates chart_metric config and fires config-changed for Lovelace persistence. Update _updateHeaderStats to accept config and switch all header stat values (consumption, upstream, downstream, solar) between kW and A display based on chart_metric, including unit label updates. Battery remains SoC % and grid state shows DSM state text. --- src/card/span-panel-card.js | 26 +++++++++- src/core/dom-updater.js | 99 +++++++++++++++++++++++++++++++------ 2 files changed, 107 insertions(+), 18 deletions(-) diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 33e5c06..2d357b8 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -30,6 +30,7 @@ export class SpanPanelCard extends HTMLElement { this._rendered = false; this._handleToggleClick = this._onToggleClick.bind(this); + this._handleUnitToggle = this._onUnitToggle.bind(this); } connectedCallback() { @@ -180,6 +181,25 @@ export class SpanPanelCard extends HTMLElement { updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory); } + // ── Unit toggle (A/W) click handler ─────────────────────────────────────── + + _onUnitToggle(event) { + const btn = event.target.closest(".unit-btn"); + if (!btn) return; + const unit = btn.dataset.unit; + if (!unit || unit === (this._config.chart_metric || "power")) return; + this._config = { ...this._config, chart_metric: unit }; + this.dispatchEvent( + new CustomEvent("config-changed", { + detail: { config: this._config }, + bubbles: true, + composed: true, + }) + ); + this._rendered = false; + this._render(); + } + // ── Toggle click handler ─────────────────────────────────────────────────── _onToggleClick(ev) { @@ -229,8 +249,9 @@ export class SpanPanelCard extends HTMLElement { const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config, null); const subDevHTML = buildSubDevicesHTML(topo, hass, this._config, durationMs); - // Remove previous listener before replacing DOM + // Remove previous listeners before replacing DOM this.shadowRoot.removeEventListener("click", this._handleToggleClick); + this.shadowRoot.removeEventListener("click", this._handleUnitToggle); this.shadowRoot.innerHTML = ` @@ -249,8 +270,9 @@ export class SpanPanelCard extends HTMLElement { `; - // Attach single delegated click listener + // Attach delegated click listeners this.shadowRoot.addEventListener("click", this._handleToggleClick); + this.shadowRoot.addEventListener("click", this._handleUnitToggle); this._rendered = true; this._recordPowerHistory(); diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index d38a9e4..ef27c51 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -19,26 +19,93 @@ function _findPanelEntity(hass, _topology, suffix) { // ── Header stats ─────────────────────────────────────────────────────────── -function _updateHeaderStats(root, hass, topology, totalConsumption, solarProduction) { - const panelPowerEntity = _findPanelEntity(hass, topology, "current_power"); - if (panelPowerEntity) { - const state = hass.states[panelPowerEntity]; - if (state) totalConsumption = Math.abs(parseFloat(state.state) || 0); - } +function _updateHeaderStats(root, hass, topology, config, totalConsumption, solarProduction) { + const isAmpsMode = (config.chart_metric || "power") === "current"; + // Site / consumption stat const consumptionEl = root.querySelector(".stat-consumption .stat-value"); - if (consumptionEl) consumptionEl.textContent = formatKw(totalConsumption); - - const currentEl = root.querySelector(".stat-current .stat-value"); - if (currentEl) { - const panelPowerEid = _findPanelEntity(hass, topology, "current_power"); - const panelPowerState = panelPowerEid ? hass.states[panelPowerEid] : null; - const amperage = panelPowerState ? parseFloat(panelPowerState.attributes?.amperage) : NaN; - currentEl.textContent = Number.isFinite(amperage) ? amperage.toFixed(1) : "--"; + const consumptionUnitEl = root.querySelector(".stat-consumption .stat-unit"); + if (isAmpsMode) { + const siteEid = _findPanelEntity(hass, topology, "current_power"); + const siteState = siteEid ? hass.states[siteEid] : null; + const amps = siteState ? parseFloat(siteState.attributes?.amperage) : NaN; + if (consumptionEl) consumptionEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; + if (consumptionUnitEl) consumptionUnitEl.textContent = "A"; + } else { + const panelPowerEntity = _findPanelEntity(hass, topology, "current_power"); + if (panelPowerEntity) { + const state = hass.states[panelPowerEntity]; + if (state) totalConsumption = Math.abs(parseFloat(state.state) || 0); + } + if (consumptionEl) consumptionEl.textContent = formatKw(totalConsumption); + if (consumptionUnitEl) consumptionUnitEl.textContent = "kW"; } + // Upstream stat + const upstreamEl = root.querySelector(".stat-upstream .stat-value"); + const upstreamUnitEl = root.querySelector(".stat-upstream .stat-unit"); + if (upstreamEl) { + const upEid = _findPanelEntity(hass, topology, "current_power"); + const upState = upEid ? hass.states[upEid] : null; + if (isAmpsMode) { + const amps = upState ? parseFloat(upState.attributes?.amperage) : NaN; + upstreamEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; + if (upstreamUnitEl) upstreamUnitEl.textContent = "A"; + } else { + const w = upState ? Math.abs(parseFloat(upState.state) || 0) : 0; + upstreamEl.textContent = formatKw(w); + if (upstreamUnitEl) upstreamUnitEl.textContent = "kW"; + } + } + + // Downstream stat + const downstreamEl = root.querySelector(".stat-downstream .stat-value"); + const downstreamUnitEl = root.querySelector(".stat-downstream .stat-unit"); + if (downstreamEl) { + const downEid = _findPanelEntity(hass, topology, "feedthrough_power"); + const downState = downEid ? hass.states[downEid] : null; + if (isAmpsMode) { + const amps = downState ? parseFloat(downState.attributes?.amperage) : NaN; + downstreamEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; + if (downstreamUnitEl) downstreamUnitEl.textContent = "A"; + } else { + const w = downState ? Math.abs(parseFloat(downState.state) || 0) : 0; + downstreamEl.textContent = formatKw(w); + if (downstreamUnitEl) downstreamUnitEl.textContent = "kW"; + } + } + + // Solar stat const solarEl = root.querySelector(".stat-solar .stat-value"); - if (solarEl) solarEl.textContent = solarProduction > 0 ? formatKw(solarProduction) : "--"; + const solarUnitEl = root.querySelector(".stat-solar .stat-unit"); + if (solarEl) { + if (isAmpsMode) { + const solarEid = _findPanelEntity(hass, topology, "solar_inverter_instant_power"); + const solarState = solarEid ? hass.states[solarEid] : null; + const amps = solarState ? parseFloat(solarState.attributes?.amperage) : NaN; + solarEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; + if (solarUnitEl) solarUnitEl.textContent = "A"; + } else { + solarEl.textContent = solarProduction > 0 ? formatKw(solarProduction) : "--"; + if (solarUnitEl) solarUnitEl.textContent = "kW"; + } + } + + // Battery SoC (always %) + const batteryEl = root.querySelector(".stat-battery .stat-value"); + if (batteryEl) { + const battEid = _findPanelEntity(hass, topology, "battery_percentage"); + const battState = battEid ? hass.states[battEid] : null; + if (battState) batteryEl.textContent = `${Math.round(parseFloat(battState.state) || 0)}`; + } + + // Grid / DSM state + const gridStateEl = root.querySelector(".stat-grid-state .stat-value"); + if (gridStateEl) { + const gridEid = _findPanelEntity(hass, topology, "dsm_state"); + const gridState = gridEid ? hass.states[gridEid] : null; + gridStateEl.textContent = gridState ? gridState.state : "--"; + } } // ── Exported updaters ────────────────────────────────────────────────────── @@ -62,7 +129,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory) { } } - _updateHeaderStats(root, hass, topology, totalConsumption, solarProduction); + _updateHeaderStats(root, hass, topology, config, totalConsumption, solarProduction); const chartMetric = getChartMetric(config); const showCurrent = chartMetric.entityRole === "current"; From e135ea2648132b57008dd575faeffca9e9370ad9 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:32:19 -0700 Subject: [PATCH 011/101] feat: add side panel web component for circuit and panel config --- src/core/side-panel.js | 515 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 src/core/side-panel.js diff --git a/src/core/side-panel.js b/src/core/side-panel.js new file mode 100644 index 0000000..7e589e8 --- /dev/null +++ b/src/core/side-panel.js @@ -0,0 +1,515 @@ +// src/core/side-panel.js +import { escapeHtml } from "../helpers/sanitize.js"; +import { INTEGRATION_DOMAIN, SHEDDING_PRIORITIES } from "../constants.js"; + +const DEBOUNCE_MS = 500; + +const PRIORITY_OPTIONS = Object.keys(SHEDDING_PRIORITIES).filter(k => k !== "unknown"); + +// ── Styles ──────────────────────────────────────────────────────────────── + +const STYLES = ` + :host { + display: block; + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: 360px; + max-width: 90vw; + z-index: 1000; + transform: translateX(100%); + transition: transform 0.3s ease; + pointer-events: none; + } + :host([open]) { + transform: translateX(0); + pointer-events: auto; + } + + .backdrop { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + z-index: -1; + } + :host([open]) .backdrop { + display: block; + } + + .panel { + height: 100%; + background: var(--card-background-color, #fff); + border-left: 1px solid var(--divider-color, #e0e0e0); + display: flex; + flex-direction: column; + overflow: hidden; + } + + .panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-bottom: 1px solid var(--divider-color, #e0e0e0); + } + .panel-header .title { + font-size: 18px; + font-weight: 500; + color: var(--primary-text-color, #212121); + margin: 0; + } + .panel-header .subtitle { + font-size: 13px; + color: var(--secondary-text-color, #727272); + margin: 2px 0 0 0; + } + .close-btn { + background: none; + border: none; + cursor: pointer; + color: var(--secondary-text-color, #727272); + padding: 4px; + line-height: 1; + font-size: 20px; + } + + .panel-body { + flex: 1; + overflow-y: auto; + padding: 16px; + } + + .section { + margin-bottom: 20px; + } + .section-label { + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + color: var(--secondary-text-color, #727272); + margin: 0 0 8px 0; + letter-spacing: 0.5px; + } + + .field-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 0; + } + .field-label { + font-size: 14px; + color: var(--primary-text-color, #212121); + } + + select { + padding: 6px 8px; + border: 1px solid var(--divider-color, #e0e0e0); + border-radius: 4px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color, #212121); + font-size: 14px; + } + + input[type="number"] { + width: 72px; + padding: 6px 8px; + border: 1px solid var(--divider-color, #e0e0e0); + border-radius: 4px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color, #212121); + font-size: 14px; + text-align: right; + } + input[type="number"]:disabled { + opacity: 0.5; + } + + .radio-group { + display: flex; + gap: 16px; + padding: 8px 0; + } + .radio-group label { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: var(--primary-text-color, #212121); + cursor: pointer; + } + + .monitoring-header { + display: flex; + align-items: center; + justify-content: space-between; + } + + .panel-mode-info { + font-size: 14px; + color: var(--primary-text-color, #212121); + line-height: 1.6; + } + .panel-mode-info p { + margin: 0 0 12px 0; + } +`; + +// ── Component ───────────────────────────────────────────────────────────── + +class SpanSidePanel extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this._hass = null; + this._config = null; + this._debounceTimers = {}; + } + + set hass(val) { + this._hass = val; + if (this.hasAttribute("open") && this._config) { + this._updateLiveState(); + } + } + + get hass() { + return this._hass; + } + + open(config) { + this._config = config; + this._render(); + // Force reflow before adding attribute so the transition animates + void this.offsetHeight; + this.setAttribute("open", ""); + } + + close() { + this.removeAttribute("open"); + this._config = null; + } + + // ── Rendering ───────────────────────────────────────────────────────── + + _render() { + const cfg = this._config; + if (!cfg) return; + + const shadow = this.shadowRoot; + shadow.innerHTML = ""; + + const style = document.createElement("style"); + style.textContent = STYLES; + shadow.appendChild(style); + + const backdrop = document.createElement("div"); + backdrop.className = "backdrop"; + backdrop.addEventListener("click", () => this.close()); + shadow.appendChild(backdrop); + + const panel = document.createElement("div"); + panel.className = "panel"; + shadow.appendChild(panel); + + if (cfg.panelMode) { + this._renderPanelMode(panel); + } else { + this._renderCircuitMode(panel, cfg); + } + } + + _renderPanelMode(panel) { + const header = this._createHeader("Panel Settings", null); + panel.appendChild(header); + + const body = document.createElement("div"); + body.className = "panel-body"; + body.innerHTML = ` +
+

Global monitoring settings are managed through the SPAN Panel integration options.

+

To change global thresholds, go to Settings → Devices & Services → SPAN Panel → Configure.

+

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to Custom mode.

+
+ `; + panel.appendChild(body); + } + + _renderCircuitMode(panel, cfg) { + const subtitle = `${escapeHtml(String(cfg.breaker_rating_a))}A \u00b7 ${escapeHtml(String(cfg.voltage))}V \u00b7 Tabs [${escapeHtml(String(cfg.tabs))}]`; + const header = this._createHeader(escapeHtml(cfg.name), subtitle); + panel.appendChild(header); + + const body = document.createElement("div"); + body.className = "panel-body"; + panel.appendChild(body); + + this._renderRelaySection(body, cfg); + this._renderSheddingSection(body, cfg); + this._renderMonitoringSection(body, cfg); + } + + _createHeader(title, subtitle) { + const header = document.createElement("div"); + header.className = "panel-header"; + + const titleWrap = document.createElement("div"); + titleWrap.innerHTML = `
${title}
` + (subtitle ? `
${subtitle}
` : ""); + + const closeBtn = document.createElement("button"); + closeBtn.className = "close-btn"; + closeBtn.innerHTML = "\u2715"; + closeBtn.addEventListener("click", () => this.close()); + + header.appendChild(titleWrap); + header.appendChild(closeBtn); + return header; + } + + // ── Relay section ─────────────────────────────────────────────────── + + _renderRelaySection(body, cfg) { + if (cfg.is_user_controllable === false || !cfg.entities?.switch) return; + + const section = document.createElement("div"); + section.className = "section"; + section.innerHTML = `
Relay
`; + + const row = document.createElement("div"); + row.className = "field-row"; + + const label = document.createElement("span"); + label.className = "field-label"; + label.textContent = "Breaker"; + + const toggle = document.createElement("ha-switch"); + toggle.dataset.role = "relay-toggle"; + const entityId = cfg.entities.switch; + const currentState = this._hass?.states?.[entityId]?.state; + if (currentState === "on") { + toggle.setAttribute("checked", ""); + } + + toggle.addEventListener("change", () => { + const isOn = toggle.hasAttribute("checked") || toggle.checked; + this._callService("switch", isOn ? "turn_on" : "turn_off", { entity_id: entityId }); + }); + + row.appendChild(label); + row.appendChild(toggle); + section.appendChild(row); + body.appendChild(section); + } + + // ── Shedding section ──────────────────────────────────────────────── + + _renderSheddingSection(body, cfg) { + if (!cfg.entities?.select) return; + + const section = document.createElement("div"); + section.className = "section"; + section.innerHTML = `
Shedding Priority
`; + + const row = document.createElement("div"); + row.className = "field-row"; + + const label = document.createElement("span"); + label.className = "field-label"; + label.textContent = "Priority"; + + const selectEl = document.createElement("select"); + selectEl.dataset.role = "shedding-select"; + const entityId = cfg.entities.select; + const currentPriority = this._hass?.states?.[entityId]?.state || ""; + + for (const key of PRIORITY_OPTIONS) { + const opt = document.createElement("option"); + opt.value = key; + opt.textContent = SHEDDING_PRIORITIES[key].label; + if (key === currentPriority) opt.selected = true; + selectEl.appendChild(opt); + } + + selectEl.addEventListener("change", () => { + this._callService("select", "select_option", { + entity_id: entityId, + option: selectEl.value, + }); + }); + + row.appendChild(label); + row.appendChild(selectEl); + section.appendChild(row); + body.appendChild(section); + } + + // ── Monitoring section ────────────────────────────────────────────── + + _renderMonitoringSection(body, cfg) { + const section = document.createElement("div"); + section.className = "section"; + + const headerRow = document.createElement("div"); + headerRow.className = "monitoring-header"; + + const sectionLabel = document.createElement("div"); + sectionLabel.className = "section-label"; + sectionLabel.textContent = "Monitoring"; + sectionLabel.style.margin = "0"; + + const enableToggle = document.createElement("ha-switch"); + enableToggle.dataset.role = "monitoring-toggle"; + + const info = cfg.monitoringInfo; + const isEnabled = info != null; + if (isEnabled) { + enableToggle.setAttribute("checked", ""); + } + + headerRow.appendChild(sectionLabel); + headerRow.appendChild(enableToggle); + section.appendChild(headerRow); + + const detailsWrap = document.createElement("div"); + detailsWrap.dataset.role = "monitoring-details"; + detailsWrap.style.display = isEnabled ? "block" : "none"; + section.appendChild(detailsWrap); + + const hasCustom = info?.continuous_threshold_pct !== undefined; + + // Global / Custom radio + const radioGroup = document.createElement("div"); + radioGroup.className = "radio-group"; + radioGroup.innerHTML = ` + + + `; + detailsWrap.appendChild(radioGroup); + + // Threshold fields + const thresholdsWrap = document.createElement("div"); + thresholdsWrap.dataset.role = "threshold-fields"; + thresholdsWrap.style.display = hasCustom ? "block" : "none"; + + const continuousVal = info?.continuous_threshold_pct ?? 80; + const spikeVal = info?.spike_threshold_pct ?? 100; + + thresholdsWrap.appendChild(this._createThresholdRow("Continuous %", "continuous", continuousVal, cfg)); + thresholdsWrap.appendChild(this._createThresholdRow("Spike %", "spike", spikeVal, cfg)); + detailsWrap.appendChild(thresholdsWrap); + + // Event: monitoring enable toggle + enableToggle.addEventListener("change", () => { + const checked = enableToggle.hasAttribute("checked") || enableToggle.checked; + detailsWrap.style.display = checked ? "block" : "none"; + if (!checked) { + this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }); + } + }); + + // Event: radio change + const radios = radioGroup.querySelectorAll('input[type="radio"]'); + for (const radio of radios) { + radio.addEventListener("change", () => { + const isCustom = radio.value === "custom" && radio.checked; + thresholdsWrap.style.display = isCustom ? "block" : "none"; + if (!isCustom && radio.checked) { + this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }); + } + }); + } + + body.appendChild(section); + } + + _createThresholdRow(label, key, value, cfg) { + const row = document.createElement("div"); + row.className = "field-row"; + + const labelEl = document.createElement("span"); + labelEl.className = "field-label"; + labelEl.textContent = label; + + const input = document.createElement("input"); + input.type = "number"; + input.min = "0"; + input.max = "200"; + input.value = String(value); + input.dataset.role = `threshold-${key}`; + + input.addEventListener("input", () => { + this._debounce(`threshold-${key}`, DEBOUNCE_MS, () => { + const continuous = this.shadowRoot.querySelector('[data-role="threshold-continuous"]'); + const spike = this.shadowRoot.querySelector('[data-role="threshold-spike"]'); + this._callDomainService("set_circuit_threshold", { + circuit_id: cfg.uuid, + continuous_threshold_pct: continuous ? Number(continuous.value) : undefined, + spike_threshold_pct: spike ? Number(spike.value) : undefined, + }); + }); + }); + + row.appendChild(labelEl); + row.appendChild(input); + return row; + } + + // ── Live state updates ────────────────────────────────────────────── + + _updateLiveState() { + if (!this._config || this._config.panelMode) return; + const cfg = this._config; + + // Update relay toggle + if (cfg.entities?.switch) { + const toggle = this.shadowRoot.querySelector('[data-role="relay-toggle"]'); + if (toggle) { + const currentState = this._hass?.states?.[cfg.entities.switch]?.state; + if (currentState === "on") { + toggle.setAttribute("checked", ""); + } else { + toggle.removeAttribute("checked"); + } + } + } + + // Update shedding select + if (cfg.entities?.select) { + const selectEl = this.shadowRoot.querySelector('[data-role="shedding-select"]'); + if (selectEl) { + const currentPriority = this._hass?.states?.[cfg.entities.select]?.state || ""; + selectEl.value = currentPriority; + } + } + } + + // ── Service calls ─────────────────────────────────────────────────── + + _callService(domain, service, data) { + if (!this._hass) return; + this._hass.callService(domain, service, data); + } + + _callDomainService(service, data) { + this._callService(INTEGRATION_DOMAIN, service, data); + } + + // ── Debounce ──────────────────────────────────────────────────────── + + _debounce(key, ms, fn) { + if (this._debounceTimers[key]) { + clearTimeout(this._debounceTimers[key]); + } + this._debounceTimers[key] = setTimeout(() => { + delete this._debounceTimers[key]; + fn(); + }, ms); + } +} + +customElements.define("span-side-panel", SpanSidePanel); From 55bb453c4828ed7acde5f544e2139ae8a65d8ce4 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:36:25 -0700 Subject: [PATCH 012/101] feat: wire gear icon clicks to side panel in card --- src/card/span-panel-card.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 2d357b8..9ca1bdf 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -9,6 +9,7 @@ import { loadHistory, collectSubDeviceEntityIds } from "../core/history-loader.j import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; import { CARD_STYLES } from "./card-styles.js"; +import "../core/side-panel.js"; export class SpanPanelCard extends HTMLElement { constructor() { @@ -31,6 +32,7 @@ export class SpanPanelCard extends HTMLElement { this._handleToggleClick = this._onToggleClick.bind(this); this._handleUnitToggle = this._onUnitToggle.bind(this); + this._handleGearClick = this._onGearClick.bind(this); } connectedCallback() { @@ -225,6 +227,34 @@ export class SpanPanelCard extends HTMLElement { }); } + // ── Gear click handler ──────────────────────────────────────────────────── + + _onGearClick(event) { + const gearBtn = event.target.closest(".gear-icon"); + if (!gearBtn) return; + + const sidePanel = this.shadowRoot.querySelector("span-side-panel"); + if (!sidePanel) return; + sidePanel.hass = this._hass; + + if (gearBtn.classList.contains("panel-gear")) { + sidePanel.open({ panelMode: true }); + return; + } + + const uuid = gearBtn.dataset.uuid; + if (!uuid || !this._topology) return; + + const circuit = this._topology.circuits[uuid]; + if (!circuit) return; + + sidePanel.open({ + ...circuit, + uuid, + monitoringInfo: null, + }); + } + // ── Full render ──────────────────────────────────────────────────────────── _render() { @@ -252,6 +282,7 @@ export class SpanPanelCard extends HTMLElement { // Remove previous listeners before replacing DOM this.shadowRoot.removeEventListener("click", this._handleToggleClick); this.shadowRoot.removeEventListener("click", this._handleUnitToggle); + this.shadowRoot.removeEventListener("click", this._handleGearClick); this.shadowRoot.innerHTML = ` @@ -268,11 +299,16 @@ export class SpanPanelCard extends HTMLElement { } ${subDevHTML ? `
${subDevHTML}
` : ""} + `; // Attach delegated click listeners this.shadowRoot.addEventListener("click", this._handleToggleClick); this.shadowRoot.addEventListener("click", this._handleUnitToggle); + this.shadowRoot.addEventListener("click", this._handleGearClick); + + const sidePanel = this.shadowRoot.querySelector("span-side-panel"); + if (sidePanel) sidePanel.hass = hass; this._rendered = true; this._recordPowerHistory(); From af1e2552262a69f551db81c7b653c84815c704b7 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:37:55 -0700 Subject: [PATCH 013/101] feat: integrate monitoring status cache into card lifecycle --- src/card/span-panel-card.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 9ca1bdf..89be3bc 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -10,6 +10,7 @@ import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; import { CARD_STYLES } from "./card-styles.js"; import "../core/side-panel.js"; +import { MonitoringStatusCache } from "../core/monitoring-status.js"; export class SpanPanelCard extends HTMLElement { constructor() { @@ -33,6 +34,7 @@ export class SpanPanelCard extends HTMLElement { this._handleToggleClick = this._onToggleClick.bind(this); this._handleUnitToggle = this._onUnitToggle.bind(this); this._handleGearClick = this._onGearClick.bind(this); + this._monitoringCache = new MonitoringStatusCache(); } connectedCallback() { @@ -56,6 +58,7 @@ export class SpanPanelCard extends HTMLElement { this._rendered = false; this._historyLoaded = false; this._powerHistory.clear(); + this._monitoringCache.clear(); } get _durationMs() { @@ -81,6 +84,9 @@ export class SpanPanelCard extends HTMLElement { this._discovering = false; this._render(); this._loadHistory(); + this._monitoringCache.fetch(hass).then(() => { + if (this._rendered) this._updateDOM(); + }); }); return; } @@ -248,10 +254,12 @@ export class SpanPanelCard extends HTMLElement { const circuit = this._topology.circuits[uuid]; if (!circuit) return; + const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.current || circuit.entities?.power] || null; + sidePanel.open({ ...circuit, uuid, - monitoringInfo: null, + monitoringInfo, }); } @@ -276,7 +284,8 @@ export class SpanPanelCard extends HTMLElement { const durationMs = this._durationMs; const headerHTML = buildHeaderHTML(topo, this._config); - const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config, null); + const monitoringStatus = this._monitoringCache.status; + const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config, monitoringStatus); const subDevHTML = buildSubDevicesHTML(topo, hass, this._config, durationMs); // Remove previous listeners before replacing DOM From 128a7e3f6bd30373f045d1efdd83e48f765e19a7 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:40:19 -0700 Subject: [PATCH 014/101] feat: add integration panel shell with tab router and multi-panel selector --- eslint.config.js | 1 + rollup.config.mjs | 28 +++++-- src/panel/index.js | 10 +++ src/panel/span-panel.js | 165 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 src/panel/index.js create mode 100644 src/panel/span-panel.js diff --git a/eslint.config.js b/eslint.config.js index e2dfda0..3db8d72 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -25,6 +25,7 @@ export default [ MutationObserver: "readonly", IntersectionObserver: "readonly", performance: "readonly", + localStorage: "readonly", }, }, rules: { diff --git a/rollup.config.mjs b/rollup.config.mjs index e620a74..e27cd67 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,13 +1,25 @@ import terser from "@rollup/plugin-terser"; const dev = process.env.ROLLUP_WATCH === "true"; +const plugins = dev ? [] : [terser()]; -export default { - input: "src/index.js", - output: { - file: "dist/span-panel-card.js", - format: "iife", - sourcemap: false, +export default [ + { + input: "src/index.js", + output: { + file: "dist/span-panel-card.js", + format: "iife", + sourcemap: false, + }, + plugins, }, - plugins: dev ? [] : [terser()], -}; + { + input: "src/panel/index.js", + output: { + file: "dist/span-panel.js", + format: "iife", + sourcemap: false, + }, + plugins, + }, +]; diff --git a/src/panel/index.js b/src/panel/index.js new file mode 100644 index 0000000..ad258a3 --- /dev/null +++ b/src/panel/index.js @@ -0,0 +1,10 @@ +import { CARD_VERSION } from "../constants.js"; +import { SpanPanelElement } from "./span-panel.js"; + +customElements.define("span-panel", SpanPanelElement); + +console.warn( + `%c SPAN-PANEL %c v${CARD_VERSION} `, + "background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;", + "background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;" +); diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js new file mode 100644 index 0000000..b826460 --- /dev/null +++ b/src/panel/span-panel.js @@ -0,0 +1,165 @@ +import { INTEGRATION_DOMAIN } from "../constants.js"; +import "../core/side-panel.js"; + +const PANEL_STYLES = ` + :host { + display: block; + padding: 16px; + max-width: 900px; + margin: 0 auto; + } + .panel-tabs { + display: flex; + gap: 0; + border-bottom: 2px solid var(--divider-color, #333); + margin-bottom: 16px; + } + .panel-tab { + padding: 8px 20px; + cursor: pointer; + font-size: 0.9em; + font-weight: 500; + color: var(--secondary-text-color); + border-bottom: 2px solid transparent; + margin-bottom: -2px; + background: none; + border-top: none; + border-left: none; + border-right: none; + } + .panel-tab.active { + color: var(--primary-color); + border-bottom-color: var(--primary-color); + } + .panel-selector { + margin-bottom: 16px; + } + .panel-selector select { + background: var(--secondary-background-color, #333); + border: 1px solid var(--divider-color); + color: var(--primary-text-color); + border-radius: 4px; + padding: 6px 12px; + font-size: 0.9em; + } + .tab-content { + min-height: 400px; + } +`; + +export class SpanPanelElement extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this._hass = null; + this._config = {}; + this._panels = []; + this._selectedPanelId = null; + this._activeTab = "dashboard"; + this._discovered = false; + } + + set hass(val) { + this._hass = val; + if (!this._discovered) { + this._discoverPanels(); + } + } + + setConfig(config) { + this._config = config || {}; + } + + async _discoverPanels() { + if (!this._hass) return; + this._discovered = true; + + const devices = await this._hass.callWS({ + type: "config/device_registry/list", + }); + this._panels = devices.filter(d => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN)); + + const stored = localStorage.getItem("span_panel_selected"); + if (stored && this._panels.some(p => p.id === stored)) { + this._selectedPanelId = stored; + } else if (this._panels.length > 0) { + this._selectedPanelId = this._panels[0].id; + } + + this._render(); + } + + _render() { + const showSelector = this._panels.length > 1; + + this.shadowRoot.innerHTML = ` + + + ${ + showSelector + ? ` +
+ +
+ ` + : "" + } + +
+ + + +
+ +
+ `; + + const select = this.shadowRoot.getElementById("panel-select"); + if (select) { + select.addEventListener("change", () => { + this._selectedPanelId = select.value; + localStorage.setItem("span_panel_selected", select.value); + this._renderTab(); + }); + } + + for (const tab of this.shadowRoot.querySelectorAll(".panel-tab")) { + tab.addEventListener("click", () => { + this._activeTab = tab.dataset.tab; + for (const t of this.shadowRoot.querySelectorAll(".panel-tab")) { + t.classList.toggle("active", t.dataset.tab === this._activeTab); + } + this._renderTab(); + }); + } + + this._renderTab(); + } + + async _renderTab() { + const container = this.shadowRoot.getElementById("tab-content"); + if (!container) return; + + switch (this._activeTab) { + case "dashboard": + container.innerHTML = `

Dashboard loading...

`; + break; + case "monitoring": + container.innerHTML = `

Monitoring tab — coming in Task 14

`; + break; + case "settings": + container.innerHTML = `

Settings tab — coming in Task 15

`; + break; + } + } +} From cf95269d9a22614f61d25b1aafe260eedfef3a40 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:43:24 -0700 Subject: [PATCH 015/101] feat: implement dashboard tab reusing core rendering modules --- src/panel/span-panel.js | 17 +++++- src/panel/tab-dashboard.js | 111 +++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 src/panel/tab-dashboard.js diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index b826460..8f2eb56 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -1,5 +1,6 @@ import { INTEGRATION_DOMAIN } from "../constants.js"; import "../core/side-panel.js"; +import { DashboardTab } from "./tab-dashboard.js"; const PANEL_STYLES = ` :host { @@ -57,6 +58,7 @@ export class SpanPanelElement extends HTMLElement { this._selectedPanelId = null; this._activeTab = "dashboard"; this._discovered = false; + this._dashboardTab = new DashboardTab(); } set hass(val) { @@ -147,13 +149,24 @@ export class SpanPanelElement extends HTMLElement { } async _renderTab() { + this._dashboardTab.stop(); + const container = this.shadowRoot.getElementById("tab-content"); if (!container) return; switch (this._activeTab) { - case "dashboard": - container.innerHTML = `

Dashboard loading...

`; + case "dashboard": { + container.innerHTML = ""; + const config = { + chart_metric: "power", + history_minutes: 5, + show_panel: true, + show_battery: true, + show_evse: true, + }; + await this._dashboardTab.render(container, this._hass, this._selectedPanelId, config); break; + } case "monitoring": container.innerHTML = `

Monitoring tab — coming in Task 14

`; break; diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js new file mode 100644 index 0000000..6daa01d --- /dev/null +++ b/src/panel/tab-dashboard.js @@ -0,0 +1,111 @@ +import { discoverTopology } from "../card/card-discovery.js"; +import { buildHeaderHTML } from "../core/header-renderer.js"; +import { buildGridHTML } from "../core/grid-renderer.js"; +import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; +import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; +import { loadHistory } from "../core/history-loader.js"; +import { MonitoringStatusCache } from "../core/monitoring-status.js"; +import { CARD_STYLES } from "../card/card-styles.js"; +import { getHistoryDurationMs, recordSample, getMaxHistoryPoints, getMinGapMs } from "../helpers/history.js"; +import { getCircuitChartEntity } from "../helpers/chart.js"; +import { LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; + +export class DashboardTab { + constructor() { + this._topology = null; + this._panelSize = 0; + this._powerHistory = new Map(); + this._monitoringCache = new MonitoringStatusCache(); + this._updateInterval = null; + this._hass = null; + this._config = null; + } + + async render(container, hass, deviceId, config) { + this.stop(); + this._hass = hass; + this._config = config; + + try { + const result = await discoverTopology(hass, deviceId); + this._topology = result.topology; + this._panelSize = result.panelSize; + } catch (err) { + container.innerHTML = `

${err.message}

`; + return; + } + + await this._monitoringCache.fetch(hass); + + const topo = this._topology; + const totalRows = Math.ceil(this._panelSize / 2); + const durationMs = getHistoryDurationMs(config); + const monitoringStatus = this._monitoringCache.status; + + const headerHTML = buildHeaderHTML(topo, config); + const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, config, monitoringStatus); + const subDevHTML = buildSubDevicesHTML(topo, hass, config, durationMs); + + container.innerHTML = ` + + ${headerHTML} + ${ + config.show_panel !== false + ? ` +
+ ${gridHTML} +
+ ` + : "" + } + ${subDevHTML ? `
${subDevHTML}
` : ""} + `; + + try { + await loadHistory(hass, topo, config, this._powerHistory); + } catch { + // Charts will populate live + } + + // Initial DOM update with history data + updateCircuitDOM(container, hass, topo, config, this._powerHistory); + updateSubDeviceDOM(container, hass, topo, config, this._powerHistory); + + // Start live update loop + this._updateInterval = setInterval(() => { + this._recordSamples(); + updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory); + updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory); + }, LIVE_SAMPLE_INTERVAL_MS); + } + + _recordSamples() { + if (!this._topology || !this._hass) return; + const durationMs = getHistoryDurationMs(this._config); + const maxPoints = getMaxHistoryPoints(durationMs); + const minGap = getMinGapMs(durationMs); + const now = Date.now(); + const cutoff = now - durationMs; + + for (const [uuid, circuit] of Object.entries(this._topology.circuits)) { + const eid = getCircuitChartEntity(circuit, this._config); + if (!eid) continue; + const state = this._hass.states[eid]; + if (!state) continue; + const val = parseFloat(state.state); + if (isNaN(val)) continue; + + const hist = this._powerHistory.get(uuid) || []; + if (hist.length > 0 && now - hist[hist.length - 1].time < minGap) continue; + + recordSample(this._powerHistory, uuid, val, now, cutoff, maxPoints); + } + } + + stop() { + if (this._updateInterval) { + clearInterval(this._updateInterval); + this._updateInterval = null; + } + } +} From 5401a1b8e29b40fae43671d9bc33f61646bbed95 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:45:31 -0700 Subject: [PATCH 016/101] feat: implement monitoring tab with overrides table --- src/panel/span-panel.js | 10 +++- src/panel/tab-monitoring.js | 102 ++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/panel/tab-monitoring.js diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 8f2eb56..11103ef 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -1,6 +1,8 @@ import { INTEGRATION_DOMAIN } from "../constants.js"; import "../core/side-panel.js"; import { DashboardTab } from "./tab-dashboard.js"; +import { MonitoringTab } from "./tab-monitoring.js"; +import { SettingsTab } from "./tab-settings.js"; const PANEL_STYLES = ` :host { @@ -59,6 +61,8 @@ export class SpanPanelElement extends HTMLElement { this._activeTab = "dashboard"; this._discovered = false; this._dashboardTab = new DashboardTab(); + this._monitoringTab = new MonitoringTab(); + this._settingsTab = new SettingsTab(); } set hass(val) { @@ -168,10 +172,12 @@ export class SpanPanelElement extends HTMLElement { break; } case "monitoring": - container.innerHTML = `

Monitoring tab — coming in Task 14

`; + container.innerHTML = ""; + await this._monitoringTab.render(container, this._hass); break; case "settings": - container.innerHTML = `

Settings tab — coming in Task 15

`; + container.innerHTML = ""; + this._settingsTab.render(container); break; } } diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js new file mode 100644 index 0000000..2522430 --- /dev/null +++ b/src/panel/tab-monitoring.js @@ -0,0 +1,102 @@ +import { INTEGRATION_DOMAIN } from "../constants.js"; +import { escapeHtml } from "../helpers/sanitize.js"; + +export class MonitoringTab { + async render(container, hass) { + let status; + try { + const resp = await hass.callService(INTEGRATION_DOMAIN, "get_monitoring_status", {}, undefined, true); + status = resp?.response || null; + } catch { + container.innerHTML = ` +
+

Monitoring

+

+ Monitoring is not enabled. Enable it in the integration's + options flow (Settings > Devices & Services > + SPAN Panel > Configure > Monitoring). +

+
+ `; + return; + } + + const circuits = status?.circuits || {}; + const mains = status?.mains || {}; + const allEntries = [...Object.entries(circuits), ...Object.entries(mains)]; + + // Filter to only entries that appear to have custom overrides + // (all monitored points appear in the response, but custom ones + // have non-default thresholds set via the override services) + const overrideRows = allEntries + .map(([entityId, info]) => { + const name = escapeHtml(info.name || entityId); + const continuous = info.continuous_threshold_pct; + const spike = info.spike_threshold_pct; + const window = info.window_duration_m; + const isMains = Object.prototype.hasOwnProperty.call(mains, entityId); + return ` + + ${name} + ${continuous ?? "--"}% + ${spike ?? "--"}% + ${window ?? "--"}m + + + + + `; + }) + .join(""); + + container.innerHTML = ` +
+

Monitoring

+

+ Global monitoring settings are managed in the integration's options flow. + All monitored circuits and mains legs are shown below. +

+ +

Monitored Points

+ ${ + allEntries.length > 0 + ? ` + + + + + + + + + + + ${overrideRows} +
NameContinuousSpikeWindow
+ ` + : ` +

+ No monitored points found. +

+ ` + } +
+ `; + + // Reset button handlers + for (const btn of container.querySelectorAll(".reset-btn")) { + btn.addEventListener("click", async () => { + const entityId = btn.dataset.entity; + const type = btn.dataset.type; + const service = type === "mains" ? "clear_mains_threshold" : "clear_circuit_threshold"; + const param = type === "mains" ? { leg: entityId } : { circuit_id: entityId }; + await hass.callService(INTEGRATION_DOMAIN, service, param); + await this.render(container, hass); + }); + } + } +} From 10df0db6041070543065c3cbadc9c03464283310 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:45:35 -0700 Subject: [PATCH 017/101] feat: implement settings tab with integration link --- src/panel/tab-settings.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/panel/tab-settings.js diff --git a/src/panel/tab-settings.js b/src/panel/tab-settings.js new file mode 100644 index 0000000..8575995 --- /dev/null +++ b/src/panel/tab-settings.js @@ -0,0 +1,17 @@ +export class SettingsTab { + render(container) { + container.innerHTML = ` +
+

Settings

+

+ General integration settings (entity naming, device prefix, + circuit numbers) are managed through the integration's options flow. +

+ + Open SPAN Panel Integration Settings → + +
+ `; + } +} From 3fc8b505b1c60f2f4631832029156ab319a8ad66 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:50:52 -0700 Subject: [PATCH 018/101] feat: add monitoring summary bar between header and circuit grid Adds a compact summary bar that appears when monitoring is active, showing circuit and mains counts alongside warning, alert, and override counts. Hidden entirely when monitoring is disabled. --- src/card/card-styles.js | 16 ++++++++++++++++ src/card/span-panel-card.js | 6 ++++-- src/core/monitoring-status.js | 28 ++++++++++++++++++++++++++++ src/panel/tab-dashboard.js | 4 +++- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/card/card-styles.js b/src/card/card-styles.js index 087a27a..bac06bb 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -87,6 +87,22 @@ export const CARD_STYLES = ` color: var(--text-primary-color, #000); } + .monitoring-summary { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 16px; + font-size: 0.8em; + background: rgba(76, 175, 80, 0.1); + border: 1px solid var(--divider-color, #333); + border-top: none; + } + .monitoring-active { color: #4caf50; } + .monitoring-counts { display: flex; gap: 12px; } + .count-warning { color: #ff9800; } + .count-alert { color: #f44336; } + .count-overrides { color: var(--secondary-text-color); } + .panel-grid { display: grid; grid-template-columns: 28px 1fr 1fr 28px; diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 89be3bc..1c8f3e4 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -10,7 +10,7 @@ import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; import { CARD_STYLES } from "./card-styles.js"; import "../core/side-panel.js"; -import { MonitoringStatusCache } from "../core/monitoring-status.js"; +import { MonitoringStatusCache, buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; export class SpanPanelCard extends HTMLElement { constructor() { @@ -283,8 +283,9 @@ export class SpanPanelCard extends HTMLElement { const totalRows = Math.ceil(this._panelSize / 2); const durationMs = this._durationMs; - const headerHTML = buildHeaderHTML(topo, this._config); + const headerHTML = buildHeaderHTML(topo, this._config, hass); const monitoringStatus = this._monitoringCache.status; + const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config, monitoringStatus); const subDevHTML = buildSubDevicesHTML(topo, hass, this._config, durationMs); @@ -297,6 +298,7 @@ export class SpanPanelCard extends HTMLElement { ${headerHTML} + ${monitoringSummaryHTML} ${ this._config.show_panel !== false ? ` diff --git a/src/core/monitoring-status.js b/src/core/monitoring-status.js index 303d9bf..ee59720 100644 --- a/src/core/monitoring-status.js +++ b/src/core/monitoring-status.js @@ -105,3 +105,31 @@ export function isAlertActive(monitoringInfo) { if (!monitoringInfo) return false; return monitoringInfo.over_threshold_since != null; } + +/** + * Build HTML for the monitoring summary bar. + * @param {object|null} status - Full monitoring status from get_monitoring_status + * @returns {string} HTML string (empty if monitoring disabled) + */ +export function buildMonitoringSummaryHTML(status) { + if (!status) return ""; + + const circuits = Object.values(status.circuits || {}); + const mains = Object.values(status.mains || {}); + const all = [...circuits, ...mains]; + + const warnings = all.filter(p => p.utilization_pct >= 80 && p.utilization_pct < 100).length; + const alerts = all.filter(p => p.utilization_pct >= 100).length; + const overrides = all.filter(p => p.continuous_threshold_pct !== undefined).length; + + return ` +
+ ✓ Monitoring · ${circuits.length} circuits · ${mains.length} mains + + ${warnings > 0 ? `${warnings} warning${warnings > 1 ? "s" : ""}` : ""} + ${alerts > 0 ? `${alerts} alert${alerts > 1 ? "s" : ""}` : ""} + ${overrides > 0 ? `${overrides} override${overrides > 1 ? "s" : ""}` : ""} + +
+ `; +} diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 6daa01d..5ee1152 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -4,7 +4,7 @@ import { buildGridHTML } from "../core/grid-renderer.js"; import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; import { loadHistory } from "../core/history-loader.js"; -import { MonitoringStatusCache } from "../core/monitoring-status.js"; +import { MonitoringStatusCache, buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; import { CARD_STYLES } from "../card/card-styles.js"; import { getHistoryDurationMs, recordSample, getMaxHistoryPoints, getMinGapMs } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; @@ -43,12 +43,14 @@ export class DashboardTab { const monitoringStatus = this._monitoringCache.status; const headerHTML = buildHeaderHTML(topo, config); + const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, config, monitoringStatus); const subDevHTML = buildSubDevicesHTML(topo, hass, config, durationMs); container.innerHTML = ` ${headerHTML} + ${monitoringSummaryHTML} ${ config.show_panel !== false ? ` From 391dc0115f379bceb12e42dc61076ff12d45b057 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:52:07 -0700 Subject: [PATCH 019/101] fix: hide header stats when corresponding entities don't exist --- src/core/header-renderer.js | 87 ++++++++++++++++++++++++++++++++++--- src/panel/tab-dashboard.js | 2 +- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index 3594a22..e00b1ca 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -1,17 +1,60 @@ import { escapeHtml } from "../helpers/sanitize.js"; +/** + * Check whether a panel-level entity with the given suffix exists in hass.states. + * Searches for entities starting with "sensor.span_panel_" and ending with "_". + * @param {object} hass - Home Assistant object + * @param {string} suffix - Entity ID suffix to match + * @returns {boolean} + */ +function _hasPanelEntity(hass, suffix) { + if (!hass?.states) return false; + for (const entityId of Object.keys(hass.states)) { + if (entityId.startsWith("sensor.span_panel_") && entityId.endsWith(`_${suffix}`)) { + return true; + } + } + return false; +} + +/** + * Check whether any solar/PV panel-level entity exists in hass.states. + * Looks for entities matching pv/solar power patterns. + * @param {object} hass - Home Assistant object + * @returns {boolean} + */ +function _hasSolarEntity(hass) { + if (!hass?.states) return false; + for (const entityId of Object.keys(hass.states)) { + if (!entityId.startsWith("sensor.span_panel_")) continue; + const local = entityId.slice("sensor.span_panel_".length); + if ((local.includes("pv") && local.includes("power")) || local.includes("solar")) { + return true; + } + } + return false; +} + /** * Build the panel header HTML with stats, gear icon, and A/W toggle. * @param {object} topology - Panel topology from WebSocket * @param {object} config - Card/panel configuration + * @param {object} hass - Home Assistant object used to conditionally show stats * @returns {string} HTML string */ -export function buildHeaderHTML(topology, config) { +export function buildHeaderHTML(topology, config, hass) { const panelName = escapeHtml(topology.device_name || "SPAN Panel"); const serial = escapeHtml(topology.serial || ""); const firmware = escapeHtml(topology.firmware || ""); const isAmpsMode = (config.chart_metric || "power") === "current"; + const hasSite = _hasPanelEntity(hass, "current_power"); + const hasGrid = _hasPanelEntity(hass, "dsm_state"); + const hasUpstream = _hasPanelEntity(hass, "current_power"); + const hasDownstream = _hasPanelEntity(hass, "feedthrough_power"); + const hasSolar = _hasSolarEntity(hass); + const hasBattery = _hasPanelEntity(hass, "battery_percentage"); + return `
@@ -23,47 +66,77 @@ export function buildHeaderHTML(topology, config) {
+ ${ + hasSite + ? `
Site
0 ${isAmpsMode ? "A" : "kW"}
-
+
` + : "" + } + ${ + hasGrid + ? `
Grid
--
-
+
` + : "" + } + ${ + hasUpstream + ? `
Upstream
-- ${isAmpsMode ? "A" : "kW"}
-
+ ` + : "" + } + ${ + hasDownstream + ? `
Downstream
-- ${isAmpsMode ? "A" : "kW"}
-
+ ` + : "" + } + ${ + hasSolar + ? `
Solar
-- ${isAmpsMode ? "A" : "kW"}
-
+ ` + : "" + } + ${ + hasBattery + ? `
Battery
%
-
+ ` + : "" + }
diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 5ee1152..5749c11 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -42,7 +42,7 @@ export class DashboardTab { const durationMs = getHistoryDurationMs(config); const monitoringStatus = this._monitoringCache.status; - const headerHTML = buildHeaderHTML(topo, config); + const headerHTML = buildHeaderHTML(topo, config, hass); const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, config, monitoringStatus); const subDevHTML = buildSubDevicesHTML(topo, hass, config, durationMs); From 80ddb444a6ef7e3263e6b76a2871a2b837a88825 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:55:35 -0700 Subject: [PATCH 020/101] fix: add missing threshold fields and error display to side panel Add window duration and cooldown fields to circuit monitoring config (cooldown is read-only/disabled as it is a global-only setting). Wrap all service calls in error handling and display inline errors that auto-dismiss after 5 seconds. --- src/core/side-panel.js | 103 +++++++++++++++++++++++++++++++++--- src/panel/tab-monitoring.js | 20 +++---- 2 files changed, 105 insertions(+), 18 deletions(-) diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 7e589e8..c8093b9 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -158,6 +158,15 @@ const STYLES = ` .panel-mode-info p { margin: 0 0 12px 0; } + + .error-msg { + color: var(--error-color, #f44336); + font-size: 0.8em; + padding: 8px; + margin: 8px 0; + background: rgba(244, 67, 54, 0.1); + border-radius: 4px; + } `; // ── Component ───────────────────────────────────────────────────────────── @@ -249,6 +258,12 @@ class SpanSidePanel extends HTMLElement { body.className = "panel-body"; panel.appendChild(body); + const errorEl = document.createElement("div"); + errorEl.className = "error-msg"; + errorEl.id = "error-msg"; + errorEl.style.display = "none"; + body.appendChild(errorEl); + this._renderRelaySection(body, cfg); this._renderSheddingSection(body, cfg); this._renderMonitoringSection(body, cfg); @@ -297,7 +312,9 @@ class SpanSidePanel extends HTMLElement { toggle.addEventListener("change", () => { const isOn = toggle.hasAttribute("checked") || toggle.checked; - this._callService("switch", isOn ? "turn_on" : "turn_off", { entity_id: entityId }); + this._callService("switch", isOn ? "turn_on" : "turn_off", { entity_id: entityId }).catch(err => + this._showError(`Relay toggle failed: ${err.message ?? err}`) + ); }); row.appendChild(label); @@ -339,7 +356,7 @@ class SpanSidePanel extends HTMLElement { this._callService("select", "select_option", { entity_id: entityId, option: selectEl.value, - }); + }).catch(err => this._showError(`Shedding update failed: ${err.message ?? err}`)); }); row.appendChild(label); @@ -398,9 +415,13 @@ class SpanSidePanel extends HTMLElement { const continuousVal = info?.continuous_threshold_pct ?? 80; const spikeVal = info?.spike_threshold_pct ?? 100; + const windowVal = info?.window_duration_m ?? 15; + const cooldownVal = info?.cooldown_duration_m ?? 15; thresholdsWrap.appendChild(this._createThresholdRow("Continuous %", "continuous", continuousVal, cfg)); thresholdsWrap.appendChild(this._createThresholdRow("Spike %", "spike", spikeVal, cfg)); + thresholdsWrap.appendChild(this._createDurationRow("Window duration", "window-m", windowVal, 1, 180, "m", cfg)); + thresholdsWrap.appendChild(this._createDurationRow("Cooldown", "cooldown-m", cooldownVal, 1, 180, "m", cfg, true)); detailsWrap.appendChild(thresholdsWrap); // Event: monitoring enable toggle @@ -408,7 +429,9 @@ class SpanSidePanel extends HTMLElement { const checked = enableToggle.hasAttribute("checked") || enableToggle.checked; detailsWrap.style.display = checked ? "block" : "none"; if (!checked) { - this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }); + this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }).catch(err => + this._showError(`Clear monitoring failed: ${err.message ?? err}`) + ); } }); @@ -419,7 +442,9 @@ class SpanSidePanel extends HTMLElement { const isCustom = radio.value === "custom" && radio.checked; thresholdsWrap.style.display = isCustom ? "block" : "none"; if (!isCustom && radio.checked) { - this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }); + this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }).catch(err => + this._showError(`Clear monitoring failed: ${err.message ?? err}`) + ); } }); } @@ -446,11 +471,13 @@ class SpanSidePanel extends HTMLElement { this._debounce(`threshold-${key}`, DEBOUNCE_MS, () => { const continuous = this.shadowRoot.querySelector('[data-role="threshold-continuous"]'); const spike = this.shadowRoot.querySelector('[data-role="threshold-spike"]'); + const windowM = this.shadowRoot.querySelector('[data-role="threshold-window-m"]'); this._callDomainService("set_circuit_threshold", { circuit_id: cfg.uuid, continuous_threshold_pct: continuous ? Number(continuous.value) : undefined, spike_threshold_pct: spike ? Number(spike.value) : undefined, - }); + window_duration_m: windowM ? Number(windowM.value) : undefined, + }).catch(err => this._showError(`Save threshold failed: ${err.message ?? err}`)); }); }); @@ -459,6 +486,53 @@ class SpanSidePanel extends HTMLElement { return row; } + _createDurationRow(label, key, value, min, max, unit, cfg, readOnly = false) { + const row = document.createElement("div"); + row.className = "field-row"; + + const labelEl = document.createElement("span"); + labelEl.className = "field-label"; + labelEl.textContent = label; + + const inputWrap = document.createElement("div"); + + const input = document.createElement("input"); + input.type = "number"; + input.min = String(min); + input.max = String(max); + input.value = String(value); + input.dataset.role = `threshold-${key}`; + if (readOnly) { + input.disabled = true; + } + + const unitSpan = document.createElement("span"); + unitSpan.textContent = unit; + + inputWrap.appendChild(input); + inputWrap.appendChild(unitSpan); + + if (!readOnly) { + input.addEventListener("input", () => { + this._debounce(`threshold-${key}`, DEBOUNCE_MS, () => { + const continuous = this.shadowRoot.querySelector('[data-role="threshold-continuous"]'); + const spike = this.shadowRoot.querySelector('[data-role="threshold-spike"]'); + const windowM = this.shadowRoot.querySelector('[data-role="threshold-window-m"]'); + this._callDomainService("set_circuit_threshold", { + circuit_id: cfg.uuid, + continuous_threshold_pct: continuous ? Number(continuous.value) : undefined, + spike_threshold_pct: spike ? Number(spike.value) : undefined, + window_duration_m: windowM ? Number(windowM.value) : undefined, + }).catch(err => this._showError(`Save threshold failed: ${err.message ?? err}`)); + }); + }); + } + + row.appendChild(labelEl); + row.appendChild(inputWrap); + return row; + } + // ── Live state updates ────────────────────────────────────────────── _updateLiveState() { @@ -491,12 +565,25 @@ class SpanSidePanel extends HTMLElement { // ── Service calls ─────────────────────────────────────────────────── _callService(domain, service, data) { - if (!this._hass) return; - this._hass.callService(domain, service, data); + if (!this._hass) return Promise.resolve(); + return Promise.resolve(this._hass.callService(domain, service, data)); } _callDomainService(service, data) { - this._callService(INTEGRATION_DOMAIN, service, data); + return this._callService(INTEGRATION_DOMAIN, service, data); + } + + // ── Error display ─────────────────────────────────────────────────── + + _showError(message) { + const el = this.shadowRoot.getElementById("error-msg"); + if (el) { + el.textContent = message; + el.style.display = "block"; + setTimeout(() => { + el.style.display = "none"; + }, 5000); + } } // ── Debounce ──────────────────────────────────────────────────────── diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 2522430..948a56b 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -25,10 +25,7 @@ export class MonitoringTab { const mains = status?.mains || {}; const allEntries = [...Object.entries(circuits), ...Object.entries(mains)]; - // Filter to only entries that appear to have custom overrides - // (all monitored points appear in the response, but custom ones - // have non-default thresholds set via the override services) - const overrideRows = allEntries + const monitoredRows = allEntries .map(([entityId, info]) => { const name = escapeHtml(info.name || entityId); const continuous = info.continuous_threshold_pct; @@ -44,8 +41,9 @@ export class MonitoringTab { @@ -57,8 +55,10 @@ export class MonitoringTab {

Monitoring

- Global monitoring settings are managed in the integration's options flow. - All monitored circuits and mains legs are shown below. + Global monitoring thresholds are configured in the integration's options flow + (Settings > Devices & Services > SPAN Panel > Configure > Monitoring). + Per-circuit overrides are listed below. Use Reset to Default to clear + a custom override and restore the circuit to global defaults.

Monitored Points

@@ -75,12 +75,12 @@ export class MonitoringTab { - ${overrideRows} + ${monitoredRows} ` : `

- No monitored points found. + All circuits using global defaults. No per-circuit overrides are configured.

` } From f6fdd73b3e6b7b1419c7eb1ded2b0ad053e822ac Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:57:48 -0700 Subject: [PATCH 021/101] feat: add global monitoring settings section and panel config link --- src/core/side-panel.js | 34 +++++++++++++++++++++++++++------- src/panel/tab-monitoring.js | 23 ++++++++++++++++------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/core/side-panel.js b/src/core/side-panel.js index c8093b9..52ac22d 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -234,18 +234,38 @@ class SpanSidePanel extends HTMLElement { } _renderPanelMode(panel) { - const header = this._createHeader("Panel Settings", null); + const header = this._createHeader("Panel Monitoring", "Global defaults for all circuits"); panel.appendChild(header); const body = document.createElement("div"); body.className = "panel-body"; - body.innerHTML = ` -
-

Global monitoring settings are managed through the SPAN Panel integration options.

-

To change global thresholds, go to Settings → Devices & Services → SPAN Panel → Configure.

-

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to Custom mode.

-
+ + const info = document.createElement("div"); + info.className = "panel-mode-info"; + info.innerHTML = ` +

Global monitoring thresholds apply to all circuits that don't have custom overrides. + Use the integration's options flow to change global settings.

+

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row + and switching to Custom mode.

`; + body.appendChild(info); + + const link = document.createElement("a"); + link.href = "/config/integrations/integration/span_panel"; + link.textContent = "Configure Global Thresholds"; + Object.assign(link.style, { + display: "inline-block", + marginTop: "8px", + padding: "8px 16px", + background: "var(--primary-color, #4dd9af)", + color: "var(--text-primary-color, #000)", + borderRadius: "4px", + textDecoration: "none", + fontSize: "0.85em", + fontWeight: "500", + }); + body.appendChild(link); + panel.appendChild(body); } diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 948a56b..79a9983 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -54,14 +54,23 @@ export class MonitoringTab { container.innerHTML = `

Monitoring

-

- Global monitoring thresholds are configured in the integration's options flow - (Settings > Devices & Services > SPAN Panel > Configure > Monitoring). - Per-circuit overrides are listed below. Use Reset to Default to clear - a custom override and restore the circuit to global defaults. -

-

Monitored Points

+
+

Global Settings

+

+ Global monitoring thresholds apply to all circuits without custom overrides. + Use the integration's options flow to change global settings. +

+ + Configure Global Thresholds + +
+ +

Per-Circuit Overrides

+

+ Use Reset to Default to clear a custom override and restore the circuit to global defaults. +

${ allEntries.length > 0 ? ` From 1e6701e61dfc2b649bfb44fadfb1f70509297b50 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 20:37:00 -0700 Subject: [PATCH 022/101] feat: use topology panel_entities instead of pattern matching Replace fragile hass.states pattern-matching helpers (_findPanelEntity, _hasPanelEntity, _hasSolarEntity) with direct lookups from topology.panel_entities. Remove the helper functions entirely and drop the now-unnecessary hass parameter from buildHeaderHTML. --- src/card/span-panel-card.js | 2 +- src/core/dom-updater.js | 26 ++++++------------- src/core/header-renderer.js | 50 ++++++------------------------------- src/panel/tab-dashboard.js | 2 +- 4 files changed, 16 insertions(+), 64 deletions(-) diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 1c8f3e4..0de7e36 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -283,7 +283,7 @@ export class SpanPanelCard extends HTMLElement { const totalRows = Math.ceil(this._panelSize / 2); const durationMs = this._durationMs; - const headerHTML = buildHeaderHTML(topo, this._config, hass); + const headerHTML = buildHeaderHTML(topo, this._config); const monitoringStatus = this._monitoringCache.status; const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config, monitoringStatus); diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index ef27c51..41565ea 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -5,18 +5,6 @@ import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; import { getHistoryDurationMs } from "../helpers/history.js"; import { updateChart } from "../chart/chart-update.js"; -// ── Private helper ───────────────────────────────────────────────────────── - -function _findPanelEntity(hass, _topology, suffix) { - if (!hass) return null; - for (const entityId of Object.keys(hass.states)) { - if (entityId.startsWith("sensor.span_panel_") && entityId.endsWith(`_${suffix}`)) { - return entityId; - } - } - return null; -} - // ── Header stats ─────────────────────────────────────────────────────────── function _updateHeaderStats(root, hass, topology, config, totalConsumption, solarProduction) { @@ -26,13 +14,13 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption, sola const consumptionEl = root.querySelector(".stat-consumption .stat-value"); const consumptionUnitEl = root.querySelector(".stat-consumption .stat-unit"); if (isAmpsMode) { - const siteEid = _findPanelEntity(hass, topology, "current_power"); + const siteEid = topology.panel_entities?.current_power; const siteState = siteEid ? hass.states[siteEid] : null; const amps = siteState ? parseFloat(siteState.attributes?.amperage) : NaN; if (consumptionEl) consumptionEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; if (consumptionUnitEl) consumptionUnitEl.textContent = "A"; } else { - const panelPowerEntity = _findPanelEntity(hass, topology, "current_power"); + const panelPowerEntity = topology.panel_entities?.current_power; if (panelPowerEntity) { const state = hass.states[panelPowerEntity]; if (state) totalConsumption = Math.abs(parseFloat(state.state) || 0); @@ -45,7 +33,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption, sola const upstreamEl = root.querySelector(".stat-upstream .stat-value"); const upstreamUnitEl = root.querySelector(".stat-upstream .stat-unit"); if (upstreamEl) { - const upEid = _findPanelEntity(hass, topology, "current_power"); + const upEid = topology.panel_entities?.current_power; const upState = upEid ? hass.states[upEid] : null; if (isAmpsMode) { const amps = upState ? parseFloat(upState.attributes?.amperage) : NaN; @@ -62,7 +50,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption, sola const downstreamEl = root.querySelector(".stat-downstream .stat-value"); const downstreamUnitEl = root.querySelector(".stat-downstream .stat-unit"); if (downstreamEl) { - const downEid = _findPanelEntity(hass, topology, "feedthrough_power"); + const downEid = topology.panel_entities?.feedthrough_power; const downState = downEid ? hass.states[downEid] : null; if (isAmpsMode) { const amps = downState ? parseFloat(downState.attributes?.amperage) : NaN; @@ -80,7 +68,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption, sola const solarUnitEl = root.querySelector(".stat-solar .stat-unit"); if (solarEl) { if (isAmpsMode) { - const solarEid = _findPanelEntity(hass, topology, "solar_inverter_instant_power"); + const solarEid = topology.panel_entities?.pv_power; const solarState = solarEid ? hass.states[solarEid] : null; const amps = solarState ? parseFloat(solarState.attributes?.amperage) : NaN; solarEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; @@ -94,7 +82,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption, sola // Battery SoC (always %) const batteryEl = root.querySelector(".stat-battery .stat-value"); if (batteryEl) { - const battEid = _findPanelEntity(hass, topology, "battery_percentage"); + const battEid = topology.panel_entities?.battery_level; const battState = battEid ? hass.states[battEid] : null; if (battState) batteryEl.textContent = `${Math.round(parseFloat(battState.state) || 0)}`; } @@ -102,7 +90,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption, sola // Grid / DSM state const gridStateEl = root.querySelector(".stat-grid-state .stat-value"); if (gridStateEl) { - const gridEid = _findPanelEntity(hass, topology, "dsm_state"); + const gridEid = topology.panel_entities?.dsm_state; const gridState = gridEid ? hass.states[gridEid] : null; gridStateEl.textContent = gridState ? gridState.state : "--"; } diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index e00b1ca..c2e41b3 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -1,59 +1,23 @@ import { escapeHtml } from "../helpers/sanitize.js"; -/** - * Check whether a panel-level entity with the given suffix exists in hass.states. - * Searches for entities starting with "sensor.span_panel_" and ending with "_". - * @param {object} hass - Home Assistant object - * @param {string} suffix - Entity ID suffix to match - * @returns {boolean} - */ -function _hasPanelEntity(hass, suffix) { - if (!hass?.states) return false; - for (const entityId of Object.keys(hass.states)) { - if (entityId.startsWith("sensor.span_panel_") && entityId.endsWith(`_${suffix}`)) { - return true; - } - } - return false; -} - -/** - * Check whether any solar/PV panel-level entity exists in hass.states. - * Looks for entities matching pv/solar power patterns. - * @param {object} hass - Home Assistant object - * @returns {boolean} - */ -function _hasSolarEntity(hass) { - if (!hass?.states) return false; - for (const entityId of Object.keys(hass.states)) { - if (!entityId.startsWith("sensor.span_panel_")) continue; - const local = entityId.slice("sensor.span_panel_".length); - if ((local.includes("pv") && local.includes("power")) || local.includes("solar")) { - return true; - } - } - return false; -} - /** * Build the panel header HTML with stats, gear icon, and A/W toggle. * @param {object} topology - Panel topology from WebSocket * @param {object} config - Card/panel configuration - * @param {object} hass - Home Assistant object used to conditionally show stats * @returns {string} HTML string */ -export function buildHeaderHTML(topology, config, hass) { +export function buildHeaderHTML(topology, config) { const panelName = escapeHtml(topology.device_name || "SPAN Panel"); const serial = escapeHtml(topology.serial || ""); const firmware = escapeHtml(topology.firmware || ""); const isAmpsMode = (config.chart_metric || "power") === "current"; - const hasSite = _hasPanelEntity(hass, "current_power"); - const hasGrid = _hasPanelEntity(hass, "dsm_state"); - const hasUpstream = _hasPanelEntity(hass, "current_power"); - const hasDownstream = _hasPanelEntity(hass, "feedthrough_power"); - const hasSolar = _hasSolarEntity(hass); - const hasBattery = _hasPanelEntity(hass, "battery_percentage"); + const hasSite = !!topology.panel_entities?.current_power; + const hasGrid = !!topology.panel_entities?.dsm_state; + const hasUpstream = !!topology.panel_entities?.current_power; + const hasDownstream = !!topology.panel_entities?.feedthrough_power; + const hasSolar = !!topology.panel_entities?.pv_power; + const hasBattery = !!topology.panel_entities?.battery_level; return `
diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 5749c11..5ee1152 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -42,7 +42,7 @@ export class DashboardTab { const durationMs = getHistoryDurationMs(config); const monitoringStatus = this._monitoringCache.status; - const headerHTML = buildHeaderHTML(topo, config, hass); + const headerHTML = buildHeaderHTML(topo, config); const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, config, monitoringStatus); const subDevHTML = buildSubDevicesHTML(topo, hass, config, durationMs); From 5a65dde0345e956142c9422e0bf6eab4944e1239 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:11:28 -0700 Subject: [PATCH 023/101] chore: track dist/ for HACS and submodule consumers --- .gitignore | 3 +-- dist/span-panel-card.js | 1 + dist/span-panel.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 dist/span-panel-card.js create mode 100644 dist/span-panel.js diff --git a/.gitignore b/.gitignore index fb8b536..33ad7ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ # Dependencies node_modules/ -# Build output -dist/ +# Build output (dist/ is tracked for HACS and submodule consumers) # Claude .claude diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js new file mode 100644 index 0000000..85e4f72 --- /dev/null +++ b/dist/span-panel-card.js @@ -0,0 +1 @@ +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const y=r.power;function b(t){return y.unit(t)}function x(t){return(t<0?"-":"")+y.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callService(e,"get_monitoring_status",{},void 0,!0);this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,y=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${b(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const D=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function D(t){return P(t,N)}function F(t){return P(t,L)}function I(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function W(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=D(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0,l=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type===i?l+=Math.abs(o):c+=Math.abs(o)}!function(t,e,n,i,s,o){const a="current"===(i.chart_metric||"power"),r=t.querySelector(".stat-consumption .stat-value"),c=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.current_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;r&&(r.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),c&&(c.textContent="A")}else{const t=n.panel_entities?.current_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}r&&(r.textContent=w(s)),c&&(c.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),d=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",d&&(d.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=w(t),d&&(d.textContent="kW")}}const p=t.querySelector(".stat-downstream .stat-value"),u=t.querySelector(".stat-downstream .stat-unit");if(p){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",u&&(u.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;p.textContent=w(t),u&&(u.textContent="kW")}}const h=t.querySelector(".stat-solar .stat-value"),f=t.querySelector(".stat-solar .stat-unit");if(h)if(a){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--",f&&(f.textContent="A")}else h.textContent=o>0?w(o):"--",f&&(f.textContent="kW");const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,s,o,c,l);const d=_(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const l=c.entities?.power,u=l?e.states[l]:null,h=u&&parseFloat(u.state)||0,f=c.device_type===i||h<0,g=c.entities?.switch,m=g?e.states[g]:null,_=m?"on"===m.state:(u?.attributes?.relay_state||c.relay_state)===n,v=s.querySelector(".power-value");if(v)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;v.innerHTML=`${d.format(i)}A`}else v.innerHTML=`${x(h)}${b(h)}`;const y=s.querySelector(".toggle-pill");if(y){y.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=y.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}s.classList.toggle("circuit-off",!_),s.classList.toggle("circuit-producer",f);const w=s.querySelector(".chart-container");if(w){j(w,e,a.get(o)||[],r,d,f,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("a");s.href="/config/integrations/integration/span_panel",s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",textDecoration:"none",fontSize:"0.85em",fontWeight:"500"}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e,!0)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.current||s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.current_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?D(l):null,_=f?F(l):null,v=I(l,e,n,new Set([d,g,m,_].filter(Boolean))),y=W(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${b(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js new file mode 100644 index 0000000..a44416d --- /dev/null +++ b/dist/span-panel.js @@ -0,0 +1 @@ +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",o="pv",i="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const o=document.createElement("div");o.className="backdrop",o.addEventListener("click",()=>this.close()),e.appendChild(o);const i=document.createElement("div");i.className="panel",e.appendChild(i),t.panelMode?this._renderPanelMode(i):this._renderCircuitMode(i,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(o);const i=document.createElement("a");i.href="/config/integrations/integration/span_panel",i.textContent="Configure Global Thresholds",Object.assign(i.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",textDecoration:"none",fontSize:"0.85em",fontWeight:"500"}),n.appendChild(i),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,o=this._createHeader(u(e.name),n);t.appendChild(o);const i=document.createElement("div");i.className="panel-body",t.appendChild(i);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",i.appendChild(a),this._renderRelaySection(i,e),this._renderSheddingSection(i,e),this._renderMonitoringSection(i,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const o=document.createElement("div");o.innerHTML=`
${t}
`+(e?`
${e}
`:"");const i=document.createElement("button");return i.className="close-btn",i.innerHTML="✕",i.addEventListener("click",()=>this.close()),n.appendChild(o),n.appendChild(i),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const o=document.createElement("div");o.className="field-row";const i=document.createElement("span");i.className="field-label",i.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),o.appendChild(i),o.appendChild(a),n.appendChild(o),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const o=document.createElement("div");o.className="field-row";const i=document.createElement("span");i.className="field-label",i.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),o.appendChild(i),o.appendChild(a),n.appendChild(o),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const i=document.createElement("div");i.className="section-label",i.textContent="Monitoring",i.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s;r&&a.setAttribute("checked",""),o.appendChild(i),o.appendChild(a),n.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,o){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:o.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),i.appendChild(a),i.appendChild(s),i}_createDurationRow(t,e,n,o,i,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(i),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const o=await t.callWS({type:`${e}/panel_topology`,device_id:n}),i=o.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(o.circuits);if(!i)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:o,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:i}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function x(t){return Math.ceil(t/2)}function _(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return x(e)===x(n)?"row-span":_(e)===_(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const o=await t.callService(e,"get_monitoring_status",{},void 0,!0);this._status=o?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,i,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,x=e.device_type===o||y<0,_=e.entities?.switch,w=_?c.states[_]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",M=u(e.name||"Unknown"),z=$(p);let E;if("current"===z.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,o=n&&parseFloat(n.state)||0;E=`${z.format(o)}A`}else E=`${v(y)}${b(y)}`;const T=l[g||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),F=L?d:"#555",A=``;let P="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);P=`${Math.round(t)}%`}const q=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${M}\n
\n
\n \n ${E}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${P}\n ${A}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},E={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,o]of Object.entries(t.entities)){if("sensor"!==o.domain)continue;const t=(o.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(o.unique_id&&e.suffixes.some(t=>o.unique_id.endsWith(t)))return n}return null}function F(t){return L(t,z)}function A(t){return L(t,E)}function P(t){return L(t,T)}function q(t){return L(t,N)}function R(t,e,n,o){const i=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(o.has(n))continue;if(!0!==i[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function j(t,e,n,o,i,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!i},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!o}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function H(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function O(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function I(t){return Math.max(500,Math.floor(t/5e3))}function W(t,e,n,o,i,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:o,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const o=[t[0]];for(let e=1;e=n&&o.push(t[e]);return o.length>e&&o.splice(0,o.length-e),o}function G(e,n,o,i,a,s,c,l){const{options:d,series:p}=function(e,n,o,i,a){o||(o=r[t]);const s=i?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==o.fixedMin&&void 0!==o.fixedMax,u=o.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>o.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=o.fixedMin,g.max=o.fixedMax),a&&"current"===o.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(o,i,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,i,a,s){if(!t||!i||!e)return;const r=H(a);let c=0,l=0;for(const[,t]of Object.entries(i.circuits)){const n=t.entities?.power;if(!n)continue;const i=e.states[n],a=i&&parseFloat(i.state)||0;t.device_type===o?l+=Math.abs(a):c+=Math.abs(a)}!function(t,e,n,o,i,a){const s="current"===(o.chart_metric||"power"),r=t.querySelector(".stat-consumption .stat-value"),c=t.querySelector(".stat-consumption .stat-unit");if(s){const t=n.panel_entities?.current_power,o=t?e.states[t]:null,i=o?parseFloat(o.attributes?.amperage):NaN;r&&(r.textContent=Number.isFinite(i)?Math.abs(i).toFixed(1):"--"),c&&(c.textContent="A")}else{const t=n.panel_entities?.current_power;if(t){const n=e.states[t];n&&(i=Math.abs(parseFloat(n.state)||0))}r&&(r.textContent=y(i)),c&&(c.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),d=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,o=t?e.states[t]:null;if(s){const t=o?parseFloat(o.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",d&&(d.textContent="A")}else{const t=o?Math.abs(parseFloat(o.state)||0):0;l.textContent=y(t),d&&(d.textContent="kW")}}const p=t.querySelector(".stat-downstream .stat-value"),u=t.querySelector(".stat-downstream .stat-unit");if(p){const t=n.panel_entities?.feedthrough_power,o=t?e.states[t]:null;if(s){const t=o?parseFloat(o.attributes?.amperage):NaN;p.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",u&&(u.textContent="A")}else{const t=o?Math.abs(parseFloat(o.state)||0):0;p.textContent=y(t),u&&(u.textContent="kW")}}const h=t.querySelector(".stat-solar .stat-value"),g=t.querySelector(".stat-solar .stat-unit");if(h)if(s){const t=n.panel_entities?.pv_power,o=t?e.states[t]:null,i=o?parseFloat(o.attributes?.amperage):NaN;h.textContent=Number.isFinite(i)?Math.abs(i).toFixed(1):"--",g&&(g.textContent="A")}else h.textContent=a>0?y(a):"--",g&&(g.textContent="kW");const m=t.querySelector(".stat-battery .stat-value");if(m){const t=n.panel_entities?.battery_level,o=t?e.states[t]:null;o&&(m.textContent=`${Math.round(parseFloat(o.state)||0)}`)}const f=t.querySelector(".stat-grid-state .stat-value");if(f){const t=n.panel_entities?.dsm_state,o=t?e.states[t]:null;f.textContent=o?o.state:"--"}}(t,e,i,a,c,l);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(i.circuits)){const i=t.querySelector(`[data-uuid="${a}"]`);if(!i)continue;const l=c.entities?.power,u=l?e.states[l]:null,h=u&&parseFloat(u.state)||0,g=c.device_type===o||h<0,m=c.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||c.relay_state)===n,x=i.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,o=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(o)}A`}else x.innerHTML=`${v(h)}${b(h)}`;const _=i.querySelector(".toggle-pill");if(_){_.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=_.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",g);const w=i.querySelector(".chart-container");if(w){G(w,e,s.get(a)||[],r,d,g,i.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,o,i){if(!n.sub_devices)return;const a=H(o);for(const[o,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${o}"]`);if(!n)continue;const r=F(s);if(r){const t=e.states[r],o=t&&parseFloat(t.state)||0,i=n.querySelector(".sub-power-value");i&&(i.innerHTML=`${v(o)} ${b(o)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,o=i.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,o,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const o=n.querySelector(`[data-eid="${t}"]`);if(!o)continue;const i=e.states[t];i&&(o.textContent=`${i.state}${i.attributes.unit_of_measurement?" "+i.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:o,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,o]of Object.entries(t.sub_devices)){const t={power:F(o)};o.type===i&&(t.soc=A(o),t.soe=P(o));for(const[o,i]of Object.entries(t))i&&e.push({entityId:i,key:`${s}${n}_${o}`})}return e}(t))e.push(o),n.set(o,a)}async function X(t,e,n,o){if(!e||!t)return;const i=H(n),a=[],s=new Map;for(const[t,o]of Object.entries(e.circuits)){const e=S(o,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;i>72e5?await async function(t,e,n,o,i){const a=new Date(Date.now()-o).toISOString(),s=o/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const o=n.get(t);if(!o||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=i.get(o)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),i.set(o,e)}}}(t,a,s,i,o):await async function(t,e,n,o,i){const a=new Date(Date.now()-o).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=O(o),c=I(o);for(const[t,e]of Object.entries(s)){const o=n.get(t);if(!o||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=i.get(o)||[],e=[...a,...t];i.set(o,D(e,r,c))}}}(t,a,s,i,o)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,o){this.stop(),this._hass=e,this._config=o;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(H(o),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),o=u(t.serial||""),i=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${t.panel_entities?.current_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${i}\n
\n \n \n
\n
\n
\n `}(s,o),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),o=[...e,...n],i=o.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=o.filter(t=>t.utilization_pct>=100).length,s=o.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${i>0?`${i} warning${i>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,o,i,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const o=Math.min(...t),i=1===t.length?"single":w(t);s.set(o,{uuid:e,circuit:n,layout:i});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,o=x(Math.max(...n));0===_(t)?c.add(o):l.add(o)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(i=a,s=e,i?.circuits&&i.circuits[s]||null):null;var i,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&o.states[r]?o.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,o,i,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,o,i,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,o,i,e,n)}p+=`
${n}
`}return p}(s,r,0,e,o,c),h=function(t,e,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===i&&!o)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===i?"Battery":"Sub-device",d=F(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===i,m=g?A(l):null,f=g?P(l):null,y=g?q(l):null,x=R(l,e,n,new Set([d,m,f,y].filter(Boolean))),_=j(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${_}\n ${x}\n
\n `}return r}(s,e,o);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==o.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n `;try{await X(e,s,o,this._powerHistory)}catch{}B(t,e,s,o,this._powerHistory),U(t,e,s,o,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=H(this._config),e=O(t),n=I(t),o=Date.now(),i=o-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=S(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&o-l[l.length-1].time\n
\n ')}const i=o?.circuits||{},a=o?.mains||{},s=[...Object.entries(i),...Object.entries(a)],r=s.map(([t,e])=>{const n=u(e.name||t),o=e.continuous_threshold_pct,i=e.spike_threshold_pct,s=e.window_duration_m,r=Object.prototype.hasOwnProperty.call(a,t);return`\n \n ${n}\n ${o??"--"}%\n ${i??"--"}%\n ${s??"--"}m\n \n \n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n

Global Settings

\n

\n Global monitoring thresholds apply to all circuits without custom overrides.\n Use the integration's options flow to change global settings.\n

\n \n Configure Global Thresholds\n \n
\n\n

Per-Circuit Overrides

\n

\n Use Reset to Default to clear a custom override and restore the circuit to global defaults.\n

\n ${s.length>0?`\n \n \n \n \n \n \n \n \n \n \n ${r}\n
NameContinuousSpikeWindow
\n `:'\n

\n All circuits using global defaults. No per-circuit overrides are configured.\n

\n '}\n
\n `;for(const o of t.querySelectorAll(".reset-btn"))o.addEventListener("click",async()=>{const i=o.dataset.entity,a=o.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:i}:{circuit_id:i};await n.callService(e,s,r),await this.render(t,n)})}}class Q{render(t){t.innerHTML='\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration\'s options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n '}}class Y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new J,this._settingsTab=new Q}set hass(t){this._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._render()}_render(){const t=this._panels.length>1;this.shadowRoot.innerHTML=`\n \n\n ${t?`\n
\n \n
\n `:""}\n\n
\n \n \n \n
\n\n
\n `;const e=this.shadowRoot.getElementById("panel-select");e&&e.addEventListener("change",()=>{this._selectedPanelId=e.value,localStorage.setItem("span_panel_selected",e.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._renderTab()}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e={chart_metric:"power",history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0};await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":t.innerHTML="",this._settingsTab.render(t)}}}customElements.define("span-panel",Y),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); From 172231ab36db975e9883acaf8170d63b8e061720 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 22:39:12 -0700 Subject: [PATCH 024/101] fix: panel dashboard smoke test fixes - Site stat reads from panel_entities.site_power instead of current_power - Solar stat reads from panel_entities.pv_power entity directly instead of summing circuit power by device_type - A/W toggle wired through panel shell with localStorage persistence - Monitoring tab uses callWS with explicit return_response schema - Monitoring tab shows inline enable toggle and editable global settings calling set_global_monitoring service instead of dead config flow link - Gear icons open side panel in dashboard; panel gear navigates to Monitoring tab - Toggle pills clickable in panel view (turn relays on/off) - Off circuits keep toggle and gear at full opacity - Panel selector always visible (label for single, dropdown for multi) - Settings tab links to specific config entry - Side panel "Configure Global Thresholds" navigates to Monitoring tab instead of crashing by navigating away from custom panel - hass reference propagated from panel shell to dashboard tab on updates --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 4 +- src/core/dom-updater.js | 30 ++++---- src/core/header-renderer.js | 2 +- src/core/monitoring-status.js | 8 +- src/core/side-panel.js | 10 ++- src/panel/span-panel.js | 81 +++++++++++++++----- src/panel/tab-dashboard.js | 55 ++++++++++++++ src/panel/tab-monitoring.js | 136 ++++++++++++++++++++++++++++------ src/panel/tab-settings.js | 6 +- 11 files changed, 272 insertions(+), 64 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 85e4f72..c01aa57 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const y=r.power;function b(t){return y.unit(t)}function x(t){return(t<0?"-":"")+y.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callService(e,"get_monitoring_status",{},void 0,!0);this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,y=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${b(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const D=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function D(t){return P(t,N)}function F(t){return P(t,L)}function I(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function W(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=D(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0,l=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type===i?l+=Math.abs(o):c+=Math.abs(o)}!function(t,e,n,i,s,o){const a="current"===(i.chart_metric||"power"),r=t.querySelector(".stat-consumption .stat-value"),c=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.current_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;r&&(r.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),c&&(c.textContent="A")}else{const t=n.panel_entities?.current_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}r&&(r.textContent=w(s)),c&&(c.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),d=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",d&&(d.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=w(t),d&&(d.textContent="kW")}}const p=t.querySelector(".stat-downstream .stat-value"),u=t.querySelector(".stat-downstream .stat-unit");if(p){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",u&&(u.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;p.textContent=w(t),u&&(u.textContent="kW")}}const h=t.querySelector(".stat-solar .stat-value"),f=t.querySelector(".stat-solar .stat-unit");if(h)if(a){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--",f&&(f.textContent="A")}else h.textContent=o>0?w(o):"--",f&&(f.textContent="kW");const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,s,o,c,l);const d=_(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const l=c.entities?.power,u=l?e.states[l]:null,h=u&&parseFloat(u.state)||0,f=c.device_type===i||h<0,g=c.entities?.switch,m=g?e.states[g]:null,_=m?"on"===m.state:(u?.attributes?.relay_state||c.relay_state)===n,v=s.querySelector(".power-value");if(v)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;v.innerHTML=`${d.format(i)}A`}else v.innerHTML=`${x(h)}${b(h)}`;const y=s.querySelector(".toggle-pill");if(y){y.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=y.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}s.classList.toggle("circuit-off",!_),s.classList.toggle("circuit-producer",f);const w=s.querySelector(".chart-container");if(w){j(w,e,a.get(o)||[],r,d,f,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("a");s.href="/config/integrations/integration/span_panel",s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",textDecoration:"none",fontSize:"0.85em",fontWeight:"500"}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e,!0)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.current||s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.current_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?D(l):null,_=f?F(l):null,v=I(l,e,n,new Set([d,g,m,_].filter(Boolean))),y=W(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${b(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const y=r.power;function b(t){return y.unit(t)}function x(t){return(t<0?"-":"")+y.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,y=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${b(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function F(t){return P(t,N)}function W(t){return P(t,L)}function D(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function I(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=F(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type!==i&&(c+=Math.abs(o))}!function(t,e,n,i,s){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=w(s)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=w(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=w(t)}else u.textContent="--";h&&(h.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?i.state:"--"}}(t,e,s,o,c);const l=_(o),d="current"===l.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const p=c.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,f=c.device_type===i||h<0,g=c.entities?.switch,m=g?e.states[g]:null,_=m?"on"===m.state:(u?.attributes?.relay_state||c.relay_state)===n,v=s.querySelector(".power-value");if(v)if(d){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;v.innerHTML=`${l.format(i)}A`}else v.innerHTML=`${x(h)}${b(h)}`;const y=s.querySelector(".toggle-pill");if(y){y.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=y.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}s.classList.toggle("circuit-off",!_),s.classList.toggle("circuit-producer",f);const w=s.querySelector(".chart-container");if(w){j(w,e,a.get(o)||[],r,l,f,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("button");s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e,!0)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.current||s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?F(l):null,_=f?W(l):null,v=D(l,e,n,new Set([d,g,m,_].filter(Boolean))),y=I(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${b(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index a44416d..e7cd4fd 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",o="pv",i="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const o=document.createElement("div");o.className="backdrop",o.addEventListener("click",()=>this.close()),e.appendChild(o);const i=document.createElement("div");i.className="panel",e.appendChild(i),t.panelMode?this._renderPanelMode(i):this._renderCircuitMode(i,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(o);const i=document.createElement("a");i.href="/config/integrations/integration/span_panel",i.textContent="Configure Global Thresholds",Object.assign(i.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",textDecoration:"none",fontSize:"0.85em",fontWeight:"500"}),n.appendChild(i),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,o=this._createHeader(u(e.name),n);t.appendChild(o);const i=document.createElement("div");i.className="panel-body",t.appendChild(i);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",i.appendChild(a),this._renderRelaySection(i,e),this._renderSheddingSection(i,e),this._renderMonitoringSection(i,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const o=document.createElement("div");o.innerHTML=`
${t}
`+(e?`
${e}
`:"");const i=document.createElement("button");return i.className="close-btn",i.innerHTML="✕",i.addEventListener("click",()=>this.close()),n.appendChild(o),n.appendChild(i),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const o=document.createElement("div");o.className="field-row";const i=document.createElement("span");i.className="field-label",i.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),o.appendChild(i),o.appendChild(a),n.appendChild(o),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const o=document.createElement("div");o.className="field-row";const i=document.createElement("span");i.className="field-label",i.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),o.appendChild(i),o.appendChild(a),n.appendChild(o),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const i=document.createElement("div");i.className="section-label",i.textContent="Monitoring",i.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s;r&&a.setAttribute("checked",""),o.appendChild(i),o.appendChild(a),n.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,o){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:o.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),i.appendChild(a),i.appendChild(s),i}_createDurationRow(t,e,n,o,i,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(i),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const o=await t.callWS({type:`${e}/panel_topology`,device_id:n}),i=o.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(o.circuits);if(!i)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:o,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:i}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function x(t){return Math.ceil(t/2)}function _(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return x(e)===x(n)?"row-span":_(e)===_(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const o=await t.callService(e,"get_monitoring_status",{},void 0,!0);this._status=o?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,i,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,x=e.device_type===o||y<0,_=e.entities?.switch,w=_?c.states[_]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",M=u(e.name||"Unknown"),z=$(p);let E;if("current"===z.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,o=n&&parseFloat(n.state)||0;E=`${z.format(o)}A`}else E=`${v(y)}${b(y)}`;const T=l[g||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),F=L?d:"#555",A=``;let P="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);P=`${Math.round(t)}%`}const q=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${M}\n
\n
\n \n ${E}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${P}\n ${A}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},E={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,o]of Object.entries(t.entities)){if("sensor"!==o.domain)continue;const t=(o.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(o.unique_id&&e.suffixes.some(t=>o.unique_id.endsWith(t)))return n}return null}function F(t){return L(t,z)}function A(t){return L(t,E)}function P(t){return L(t,T)}function q(t){return L(t,N)}function R(t,e,n,o){const i=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(o.has(n))continue;if(!0!==i[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function j(t,e,n,o,i,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!i},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!o}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function H(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function O(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function I(t){return Math.max(500,Math.floor(t/5e3))}function W(t,e,n,o,i,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:o,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const o=[t[0]];for(let e=1;e=n&&o.push(t[e]);return o.length>e&&o.splice(0,o.length-e),o}function G(e,n,o,i,a,s,c,l){const{options:d,series:p}=function(e,n,o,i,a){o||(o=r[t]);const s=i?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==o.fixedMin&&void 0!==o.fixedMax,u=o.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>o.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=o.fixedMin,g.max=o.fixedMax),a&&"current"===o.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(o,i,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,i,a,s){if(!t||!i||!e)return;const r=H(a);let c=0,l=0;for(const[,t]of Object.entries(i.circuits)){const n=t.entities?.power;if(!n)continue;const i=e.states[n],a=i&&parseFloat(i.state)||0;t.device_type===o?l+=Math.abs(a):c+=Math.abs(a)}!function(t,e,n,o,i,a){const s="current"===(o.chart_metric||"power"),r=t.querySelector(".stat-consumption .stat-value"),c=t.querySelector(".stat-consumption .stat-unit");if(s){const t=n.panel_entities?.current_power,o=t?e.states[t]:null,i=o?parseFloat(o.attributes?.amperage):NaN;r&&(r.textContent=Number.isFinite(i)?Math.abs(i).toFixed(1):"--"),c&&(c.textContent="A")}else{const t=n.panel_entities?.current_power;if(t){const n=e.states[t];n&&(i=Math.abs(parseFloat(n.state)||0))}r&&(r.textContent=y(i)),c&&(c.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),d=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,o=t?e.states[t]:null;if(s){const t=o?parseFloat(o.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",d&&(d.textContent="A")}else{const t=o?Math.abs(parseFloat(o.state)||0):0;l.textContent=y(t),d&&(d.textContent="kW")}}const p=t.querySelector(".stat-downstream .stat-value"),u=t.querySelector(".stat-downstream .stat-unit");if(p){const t=n.panel_entities?.feedthrough_power,o=t?e.states[t]:null;if(s){const t=o?parseFloat(o.attributes?.amperage):NaN;p.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",u&&(u.textContent="A")}else{const t=o?Math.abs(parseFloat(o.state)||0):0;p.textContent=y(t),u&&(u.textContent="kW")}}const h=t.querySelector(".stat-solar .stat-value"),g=t.querySelector(".stat-solar .stat-unit");if(h)if(s){const t=n.panel_entities?.pv_power,o=t?e.states[t]:null,i=o?parseFloat(o.attributes?.amperage):NaN;h.textContent=Number.isFinite(i)?Math.abs(i).toFixed(1):"--",g&&(g.textContent="A")}else h.textContent=a>0?y(a):"--",g&&(g.textContent="kW");const m=t.querySelector(".stat-battery .stat-value");if(m){const t=n.panel_entities?.battery_level,o=t?e.states[t]:null;o&&(m.textContent=`${Math.round(parseFloat(o.state)||0)}`)}const f=t.querySelector(".stat-grid-state .stat-value");if(f){const t=n.panel_entities?.dsm_state,o=t?e.states[t]:null;f.textContent=o?o.state:"--"}}(t,e,i,a,c,l);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(i.circuits)){const i=t.querySelector(`[data-uuid="${a}"]`);if(!i)continue;const l=c.entities?.power,u=l?e.states[l]:null,h=u&&parseFloat(u.state)||0,g=c.device_type===o||h<0,m=c.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||c.relay_state)===n,x=i.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,o=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(o)}A`}else x.innerHTML=`${v(h)}${b(h)}`;const _=i.querySelector(".toggle-pill");if(_){_.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=_.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",g);const w=i.querySelector(".chart-container");if(w){G(w,e,s.get(a)||[],r,d,g,i.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,o,i){if(!n.sub_devices)return;const a=H(o);for(const[o,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${o}"]`);if(!n)continue;const r=F(s);if(r){const t=e.states[r],o=t&&parseFloat(t.state)||0,i=n.querySelector(".sub-power-value");i&&(i.innerHTML=`${v(o)} ${b(o)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,o=i.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,o,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const o=n.querySelector(`[data-eid="${t}"]`);if(!o)continue;const i=e.states[t];i&&(o.textContent=`${i.state}${i.attributes.unit_of_measurement?" "+i.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:o,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,o]of Object.entries(t.sub_devices)){const t={power:F(o)};o.type===i&&(t.soc=A(o),t.soe=P(o));for(const[o,i]of Object.entries(t))i&&e.push({entityId:i,key:`${s}${n}_${o}`})}return e}(t))e.push(o),n.set(o,a)}async function X(t,e,n,o){if(!e||!t)return;const i=H(n),a=[],s=new Map;for(const[t,o]of Object.entries(e.circuits)){const e=S(o,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;i>72e5?await async function(t,e,n,o,i){const a=new Date(Date.now()-o).toISOString(),s=o/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const o=n.get(t);if(!o||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=i.get(o)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),i.set(o,e)}}}(t,a,s,i,o):await async function(t,e,n,o,i){const a=new Date(Date.now()-o).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=O(o),c=I(o);for(const[t,e]of Object.entries(s)){const o=n.get(t);if(!o||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=i.get(o)||[],e=[...a,...t];i.set(o,D(e,r,c))}}}(t,a,s,i,o)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,o){this.stop(),this._hass=e,this._config=o;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(H(o),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),o=u(t.serial||""),i=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${t.panel_entities?.current_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${i}\n
\n \n \n
\n
\n
\n `}(s,o),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),o=[...e,...n],i=o.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=o.filter(t=>t.utilization_pct>=100).length,s=o.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${i>0?`${i} warning${i>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,o,i,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const o=Math.min(...t),i=1===t.length?"single":w(t);s.set(o,{uuid:e,circuit:n,layout:i});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,o=x(Math.max(...n));0===_(t)?c.add(o):l.add(o)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(i=a,s=e,i?.circuits&&i.circuits[s]||null):null;var i,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&o.states[r]?o.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,o,i,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,o,i,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,o,i,e,n)}p+=`
${n}
`}return p}(s,r,0,e,o,c),h=function(t,e,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===i&&!o)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===i?"Battery":"Sub-device",d=F(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===i,m=g?A(l):null,f=g?P(l):null,y=g?q(l):null,x=R(l,e,n,new Set([d,m,f,y].filter(Boolean))),_=j(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${_}\n ${x}\n
\n `}return r}(s,e,o);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==o.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n `;try{await X(e,s,o,this._powerHistory)}catch{}B(t,e,s,o,this._powerHistory),U(t,e,s,o,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=H(this._config),e=O(t),n=I(t),o=Date.now(),i=o-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=S(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&o-l[l.length-1].time\n
\n ')}const i=o?.circuits||{},a=o?.mains||{},s=[...Object.entries(i),...Object.entries(a)],r=s.map(([t,e])=>{const n=u(e.name||t),o=e.continuous_threshold_pct,i=e.spike_threshold_pct,s=e.window_duration_m,r=Object.prototype.hasOwnProperty.call(a,t);return`\n \n ${n}\n ${o??"--"}%\n ${i??"--"}%\n ${s??"--"}m\n \n \n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n

Global Settings

\n

\n Global monitoring thresholds apply to all circuits without custom overrides.\n Use the integration's options flow to change global settings.\n

\n \n Configure Global Thresholds\n \n
\n\n

Per-Circuit Overrides

\n

\n Use Reset to Default to clear a custom override and restore the circuit to global defaults.\n

\n ${s.length>0?`\n \n \n \n \n \n \n \n \n \n \n ${r}\n
NameContinuousSpikeWindow
\n `:'\n

\n All circuits using global defaults. No per-circuit overrides are configured.\n

\n '}\n
\n `;for(const o of t.querySelectorAll(".reset-btn"))o.addEventListener("click",async()=>{const i=o.dataset.entity,a=o.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:i}:{circuit_id:i};await n.callService(e,s,r),await this.render(t,n)})}}class Q{render(t){t.innerHTML='\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration\'s options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n '}}class Y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new J,this._settingsTab=new Q}set hass(t){this._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._render()}_render(){const t=this._panels.length>1;this.shadowRoot.innerHTML=`\n \n\n ${t?`\n
\n \n
\n `:""}\n\n
\n \n \n \n
\n\n
\n `;const e=this.shadowRoot.getElementById("panel-select");e&&e.addEventListener("change",()=>{this._selectedPanelId=e.value,localStorage.setItem("span_panel_selected",e.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._renderTab()}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e={chart_metric:"power",history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0};await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":t.innerHTML="",this._settingsTab.render(t)}}}customElements.define("span-panel",Y),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",s="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const s=document.createElement("select");s.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),s.appendChild(e)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:s.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&s.setAttribute("checked",""),i.appendChild(o),i.appendChild(s),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,g=a?.window_duration_m??15,m=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(s),o.appendChild(a),o}_createDurationRow(t,e,n,i,o,s,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=s,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,s,a,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${v(y)}${b(y)}`;const T=l[g||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=L?d:"#555",F=``;let I="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);I=`${Math.round(t)}%`}const R=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${I}\n ${F}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return L(t,M)}function F(t){return L(t,z)}function I(t){return L(t,T)}function R(t){return L(t,N)}function A(t,e,n,i){const o=n.visible_sub_entities||{};let s="";if(!t.entities)return s;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return s}function P(t,e,n,i,o,s){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!o},{key:`${a}${t}_soe`,title:"SoE",available:!!s},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function H(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function O(t){return Math.max(500,Math.floor(t/5e3))}function W(t,e,n,i,o,s){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,s,a,c,l){const{options:d,series:p}=function(e,n,i,o,s){i||(i=r[t]);const a=o?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),s&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*s),h.push({type:"line",data:[[d,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,s,a){if(!t||!o||!e)return;const r=j(s);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],s=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(s))}!function(t,e,n,i,o){const s="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(s){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,s,c);const l=$(s),d="current"===l.entityRole;for(const[s,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${s}"]`);if(!o)continue;const p=c.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,g=c.device_type===i||h<0,m=c.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||c.relay_state)===n,_=o.querySelector(".power-value");if(_)if(d){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;_.innerHTML=`${l.format(i)}A`}else _.innerHTML=`${v(h)}${b(h)}`;const x=o.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=x.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",g);const w=o.querySelector(".chart-container");if(w){G(w,e,a.get(s)||[],r,l,g,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const s=j(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,s,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:s}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=F(i),t.soe=I(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${a}${n}_${i}`})}return e}(t))e.push(i),n.set(i,s)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),s=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(s.push(e),a.set(e,t))}if(V(e,s,a),0===s.length)return;o>72e5?await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,s,a,o,i):await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:s,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=H(i),c=O(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];o.set(i,D(e,r,c))}}}(t,s,a,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const a=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),s="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(a,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,s=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${s>0?`${s} alert${s>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,s){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);a.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=s?(o=s,a=e,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,s=a.get(e),u=a.get(n);if(p+=`
${e}
`,s&&"row-span"===s.layout){const{monInfo:e,sheddingPriority:a}=d(s);p+=C(s.uuid,s.circuit,t,"2 / 4","row-span",0,i,o,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(s);p+=C(s.uuid,s.circuit,t,"2",s.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(a,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===s&&!a)continue;const t=l.type===s?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?F(l):null,f=g?I(l):null,y=g?R(l):null,_=A(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(a,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,a),this._bindToggleClicks(t,a);try{await X(e,a,i,this._powerHistory)}catch{}B(t,e,a,i,this._powerHistory),U(t,e,a,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,a,this._config,this._powerHistory),U(t,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=H(t),n=O(t),i=Date.now(),o=i-t;for(const[t,s]of Object.entries(this._topology.circuits)){const a=S(s,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,s=e.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a})})}_bindGearClicks(t,e){t.addEventListener("click",n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!e)return;const a=e.circuits[s];if(!a)return;const r=this._monitoringCache?.status?.circuits?.[a.entities?.current||a.entities?.power]||null;o.open({...a,uuid:s,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";class Z{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},s=!0===i?.enabled,a=i?.circuits||{},r=i?.mains||{},c=[...Object.entries(a),...Object.entries(r)],l=c.map(([t,e])=>{const n=u(e.name||t),i=e.continuous_threshold_pct,o=e.spike_threshold_pct,s=e.window_duration_m,a=Object.prototype.hasOwnProperty.call(r,t);return`\n \n ${n}\n ${i??"--"}%\n ${o??"--"}%\n ${s??"--"}m\n \n \n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Per-Circuit Overrides

\n

\n Use Reset to Default to clear a custom override and restore the circuit to global defaults.\n

\n ${c.length>0?`\n \n \n \n \n \n \n \n \n \n \n ${l}\n
NameContinuousSpikeWindow
\n `:'\n

\n All circuits using global defaults. No per-circuit overrides are configured.\n

\n '}\n
\n `,this._bindGlobalControls(t,n),this._bindResetButtons(t,n)}_bindGlobalControls(t,n){const i=t.querySelector("#monitoring-enabled"),o=t.querySelector("#global-fields"),s=t.querySelector("#global-status"),a=async()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await n.callService(e,"set_global_monitoring",i),s.textContent="Saved",s.style.color="var(--success-color, #4caf50)",setTimeout(()=>{s.textContent=""},2e3)}catch(t){s.textContent=`Error: ${t.message||"Failed to save"}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const e=i.checked;o.style.opacity=e?"":"0.4",o.style.pointerEvents=e?"":"none",e&&(await a(),await this.render(t,n))});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,s=i.dataset.type,a="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===s?{leg:o}:{circuit_id:o};await n.callService(e,a,r),await this.render(t,n)})}}class tt{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class et extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new Z,this._settingsTab=new tt}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",et),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index bac06bb..407324d 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -137,7 +137,9 @@ export const CARD_STYLES = ` .circuit-off .circuit-name, .circuit-off .breaker-badge, .circuit-off .power-value, - .circuit-off .chart-container { opacity: 0.45; } + .circuit-off .chart-container { opacity: 0.35; } + .circuit-off .toggle-pill, + .circuit-off .gear-icon { opacity: 1; } .circuit-empty { opacity: 0.2; diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index 41565ea..fda40e9 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -7,22 +7,22 @@ import { updateChart } from "../chart/chart-update.js"; // ── Header stats ─────────────────────────────────────────────────────────── -function _updateHeaderStats(root, hass, topology, config, totalConsumption, solarProduction) { +function _updateHeaderStats(root, hass, topology, config, totalConsumption) { const isAmpsMode = (config.chart_metric || "power") === "current"; // Site / consumption stat const consumptionEl = root.querySelector(".stat-consumption .stat-value"); const consumptionUnitEl = root.querySelector(".stat-consumption .stat-unit"); if (isAmpsMode) { - const siteEid = topology.panel_entities?.current_power; + const siteEid = topology.panel_entities?.site_power; const siteState = siteEid ? hass.states[siteEid] : null; const amps = siteState ? parseFloat(siteState.attributes?.amperage) : NaN; if (consumptionEl) consumptionEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; if (consumptionUnitEl) consumptionUnitEl.textContent = "A"; } else { - const panelPowerEntity = topology.panel_entities?.current_power; - if (panelPowerEntity) { - const state = hass.states[panelPowerEntity]; + const siteEid = topology.panel_entities?.site_power; + if (siteEid) { + const state = hass.states[siteEid]; if (state) totalConsumption = Math.abs(parseFloat(state.state) || 0); } if (consumptionEl) consumptionEl.textContent = formatKw(totalConsumption); @@ -63,18 +63,23 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption, sola } } - // Solar stat + // Solar stat — always read from panel-level PV power entity const solarEl = root.querySelector(".stat-solar .stat-value"); const solarUnitEl = root.querySelector(".stat-solar .stat-unit"); if (solarEl) { + const solarEid = topology.panel_entities?.pv_power; + const solarState = solarEid ? hass.states[solarEid] : null; if (isAmpsMode) { - const solarEid = topology.panel_entities?.pv_power; - const solarState = solarEid ? hass.states[solarEid] : null; const amps = solarState ? parseFloat(solarState.attributes?.amperage) : NaN; solarEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; if (solarUnitEl) solarUnitEl.textContent = "A"; } else { - solarEl.textContent = solarProduction > 0 ? formatKw(solarProduction) : "--"; + if (solarState) { + const w = Math.abs(parseFloat(solarState.state) || 0); + solarEl.textContent = formatKw(w); + } else { + solarEl.textContent = "--"; + } if (solarUnitEl) solarUnitEl.textContent = "kW"; } } @@ -103,21 +108,18 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory) { const durationMs = getHistoryDurationMs(config); let totalConsumption = 0; - let solarProduction = 0; for (const [, circuit] of Object.entries(topology.circuits)) { const entityId = circuit.entities?.power; if (!entityId) continue; const state = hass.states[entityId]; const power = state ? parseFloat(state.state) || 0 : 0; - if (circuit.device_type === DEVICE_TYPE_PV) { - solarProduction += Math.abs(power); - } else { + if (circuit.device_type !== DEVICE_TYPE_PV) { totalConsumption += Math.abs(power); } } - _updateHeaderStats(root, hass, topology, config, totalConsumption, solarProduction); + _updateHeaderStats(root, hass, topology, config, totalConsumption); const chartMetric = getChartMetric(config); const showCurrent = chartMetric.entityRole === "current"; diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index c2e41b3..dc2027a 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -12,7 +12,7 @@ export function buildHeaderHTML(topology, config) { const firmware = escapeHtml(topology.firmware || ""); const isAmpsMode = (config.chart_metric || "power") === "current"; - const hasSite = !!topology.panel_entities?.current_power; + const hasSite = !!topology.panel_entities?.site_power; const hasGrid = !!topology.panel_entities?.dsm_state; const hasUpstream = !!topology.panel_entities?.current_power; const hasDownstream = !!topology.panel_entities?.feedthrough_power; diff --git a/src/core/monitoring-status.js b/src/core/monitoring-status.js index ee59720..99592df 100644 --- a/src/core/monitoring-status.js +++ b/src/core/monitoring-status.js @@ -28,7 +28,13 @@ export class MonitoringStatusCache { this._fetching = true; try { - const resp = await hass.callService(INTEGRATION_DOMAIN, "get_monitoring_status", {}, undefined, true); + const resp = await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "get_monitoring_status", + service_data: {}, + return_response: true, + }); this._status = resp?.response || null; this._lastFetch = now; } catch { diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 52ac22d..6aee680 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -250,8 +250,7 @@ class SpanSidePanel extends HTMLElement { `; body.appendChild(info); - const link = document.createElement("a"); - link.href = "/config/integrations/integration/span_panel"; + const link = document.createElement("button"); link.textContent = "Configure Global Thresholds"; Object.assign(link.style, { display: "inline-block", @@ -260,10 +259,15 @@ class SpanSidePanel extends HTMLElement { background: "var(--primary-color, #4dd9af)", color: "var(--text-primary-color, #000)", borderRadius: "4px", - textDecoration: "none", + border: "none", + cursor: "pointer", fontSize: "0.85em", fontWeight: "500", }); + link.addEventListener("click", () => { + this.close(); + this.dispatchEvent(new CustomEvent("navigate-tab", { detail: "monitoring", bubbles: true, composed: true })); + }); body.appendChild(link); panel.appendChild(body); diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 11103ef..424b61a 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -45,6 +45,11 @@ const PANEL_STYLES = ` padding: 6px 12px; font-size: 0.9em; } + .panel-label { + font-size: 0.9em; + font-weight: 500; + color: var(--secondary-text-color); + } .tab-content { min-height: 400px; } @@ -67,6 +72,7 @@ export class SpanPanelElement extends HTMLElement { set hass(val) { this._hass = val; + this._dashboardTab._hass = val; if (!this._discovered) { this._discoverPanels(); } @@ -92,19 +98,23 @@ export class SpanPanelElement extends HTMLElement { this._selectedPanelId = this._panels[0].id; } + this._chartMetric = localStorage.getItem("span_panel_metric") || "power"; + this._render(); } _render() { - const showSelector = this._panels.length > 1; + const multiPanel = this._panels.length > 1; + const selectedPanel = this._panels.find(p => p.id === this._selectedPanelId); + const panelLabel = selectedPanel ? selectedPanel.name_by_user || selectedPanel.name || selectedPanel.id : ""; this.shadowRoot.innerHTML = ` - ${ - showSelector - ? ` -
+
+ ${ + multiPanel + ? ` -
- ` - : "" - } + ` + : `${panelLabel}` + } +
@@ -149,9 +159,47 @@ export class SpanPanelElement extends HTMLElement { }); } + this._bindUnitToggle(); + this._bindTabNavigation(); this._renderTab(); } + _bindUnitToggle() { + this.shadowRoot.addEventListener("click", e => { + const btn = e.target.closest(".unit-btn"); + if (!btn) return; + const metric = btn.dataset.unit; + if (!metric || metric === this._chartMetric) return; + this._chartMetric = metric; + localStorage.setItem("span_panel_metric", metric); + if (this._activeTab === "dashboard") { + this._renderTab(); + } + }); + } + + _bindTabNavigation() { + this.shadowRoot.addEventListener("navigate-tab", e => { + const tab = e.detail; + if (!tab) return; + this._activeTab = tab; + for (const t of this.shadowRoot.querySelectorAll(".panel-tab")) { + t.classList.toggle("active", t.dataset.tab === tab); + } + this._renderTab(); + }); + } + + _buildDashboardConfig() { + return { + chart_metric: this._chartMetric, + history_minutes: 5, + show_panel: true, + show_battery: true, + show_evse: true, + }; + } + async _renderTab() { this._dashboardTab.stop(); @@ -161,13 +209,7 @@ export class SpanPanelElement extends HTMLElement { switch (this._activeTab) { case "dashboard": { container.innerHTML = ""; - const config = { - chart_metric: "power", - history_minutes: 5, - show_panel: true, - show_battery: true, - show_evse: true, - }; + const config = this._buildDashboardConfig(); await this._dashboardTab.render(container, this._hass, this._selectedPanelId, config); break; } @@ -175,10 +217,13 @@ export class SpanPanelElement extends HTMLElement { container.innerHTML = ""; await this._monitoringTab.render(container, this._hass); break; - case "settings": + case "settings": { container.innerHTML = ""; - this._settingsTab.render(container); + const selectedDevice = this._panels.find(p => p.id === this._selectedPanelId); + const configEntryId = selectedDevice?.config_entries?.[0] || null; + this._settingsTab.render(container, configEntryId); break; + } } } } diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 5ee1152..8bd26f1 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -9,6 +9,7 @@ import { CARD_STYLES } from "../card/card-styles.js"; import { getHistoryDurationMs, recordSample, getMaxHistoryPoints, getMinGapMs } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; import { LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; +import "../core/side-panel.js"; export class DashboardTab { constructor() { @@ -61,8 +62,12 @@ export class DashboardTab { : "" } ${subDevHTML ? `
${subDevHTML}
` : ""} + `; + this._bindGearClicks(container, topo); + this._bindToggleClicks(container, topo); + try { await loadHistory(hass, topo, config, this._powerHistory); } catch { @@ -104,6 +109,56 @@ export class DashboardTab { } } + _bindToggleClicks(container, topology) { + container.addEventListener("click", e => { + const pill = e.target.closest(".toggle-pill"); + if (!pill) return; + e.stopPropagation(); + e.preventDefault(); + const slot = pill.closest("[data-uuid]"); + if (!slot || !topology || !this._hass) return; + const uuid = slot.dataset.uuid; + const circuit = topology.circuits[uuid]; + if (!circuit) return; + const switchEntity = circuit.entities?.switch; + if (!switchEntity) return; + const switchState = this._hass.states[switchEntity]; + if (!switchState) return; + const service = switchState.state === "on" ? "turn_off" : "turn_on"; + this._hass.callService("switch", service, {}, { entity_id: switchEntity }); + }); + } + + _bindGearClicks(container, topology) { + container.addEventListener("click", e => { + const gearBtn = e.target.closest(".gear-icon"); + if (!gearBtn) return; + + const sidePanel = container.querySelector("span-side-panel"); + if (!sidePanel || !this._hass) return; + sidePanel.hass = this._hass; + + if (gearBtn.classList.contains("panel-gear")) { + container.dispatchEvent(new CustomEvent("navigate-tab", { detail: "monitoring", bubbles: true, composed: true })); + return; + } + + const uuid = gearBtn.dataset.uuid; + if (!uuid || !topology) return; + + const circuit = topology.circuits[uuid]; + if (!circuit) return; + + const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.current || circuit.entities?.power] || null; + + sidePanel.open({ + ...circuit, + uuid, + monitoringInfo, + }); + }); + } + stop() { if (this._updateInterval) { clearInterval(this._updateInterval); diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 79a9983..f87315f 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -1,26 +1,41 @@ import { INTEGRATION_DOMAIN } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; +const FIELD_STYLE = ` + display:flex;align-items:center;gap:8px;margin-bottom:8px; +`; +const INPUT_STYLE = ` + background:var(--secondary-background-color,#333); + border:1px solid var(--divider-color); + color:var(--primary-text-color); + border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em; +`; +const LABEL_STYLE = ` + min-width:130px;font-size:0.85em;color:var(--secondary-text-color); +`; + export class MonitoringTab { + constructor() { + this._debounceTimer = null; + } + async render(container, hass) { let status; try { - const resp = await hass.callService(INTEGRATION_DOMAIN, "get_monitoring_status", {}, undefined, true); + const resp = await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "get_monitoring_status", + service_data: {}, + return_response: true, + }); status = resp?.response || null; } catch { - container.innerHTML = ` -
-

Monitoring

-

- Monitoring is not enabled. Enable it in the integration's - options flow (Settings > Devices & Services > - SPAN Panel > Configure > Monitoring). -

-
- `; - return; + status = null; } + const globalSettings = status?.global_settings || {}; + const isEnabled = status?.enabled === true; const circuits = status?.circuits || {}; const mains = status?.mains || {}; const allEntries = [...Object.entries(circuits), ...Object.entries(mains)]; @@ -56,15 +71,43 @@ export class MonitoringTab {

Monitoring

-

Global Settings

-

- Global monitoring thresholds apply to all circuits without custom overrides. - Use the integration's options flow to change global settings. -

- - Configure Global Thresholds - +
+

Global Settings

+ +
+ +
+
+ Continuous (%) + +
+
+ Spike (%) + +
+
+ Window (min) + +
+
+ Cooldown (min) + +
+
+ +

Per-Circuit Overrides

@@ -96,7 +139,56 @@ export class MonitoringTab {
`; - // Reset button handlers + this._bindGlobalControls(container, hass); + this._bindResetButtons(container, hass); + } + + _bindGlobalControls(container, hass) { + const enabledCheckbox = container.querySelector("#monitoring-enabled"); + const fieldsDiv = container.querySelector("#global-fields"); + const statusEl = container.querySelector("#global-status"); + + const saveGlobal = async () => { + clearTimeout(this._debounceTimer); + this._debounceTimer = setTimeout(async () => { + const data = { + continuous_threshold_pct: parseInt(container.querySelector("#g-continuous").value, 10), + spike_threshold_pct: parseInt(container.querySelector("#g-spike").value, 10), + window_duration_m: parseInt(container.querySelector("#g-window").value, 10), + cooldown_duration_m: parseInt(container.querySelector("#g-cooldown").value, 10), + }; + try { + await hass.callService(INTEGRATION_DOMAIN, "set_global_monitoring", data); + statusEl.textContent = "Saved"; + statusEl.style.color = "var(--success-color, #4caf50)"; + setTimeout(() => { + statusEl.textContent = ""; + }, 2000); + } catch (err) { + statusEl.textContent = `Error: ${err.message || "Failed to save"}`; + statusEl.style.color = "var(--error-color, #f44336)"; + } + }, 500); + }; + + if (enabledCheckbox) { + enabledCheckbox.addEventListener("change", async () => { + const enabled = enabledCheckbox.checked; + fieldsDiv.style.opacity = enabled ? "" : "0.4"; + fieldsDiv.style.pointerEvents = enabled ? "" : "none"; + if (enabled) { + await saveGlobal(); + await this.render(container, hass); + } + }); + } + + for (const input of container.querySelectorAll("#global-fields input[type=number]")) { + input.addEventListener("input", saveGlobal); + } + } + + _bindResetButtons(container, hass) { for (const btn of container.querySelectorAll(".reset-btn")) { btn.addEventListener("click", async () => { const entityId = btn.dataset.entity; diff --git a/src/panel/tab-settings.js b/src/panel/tab-settings.js index 8575995..fd0cc61 100644 --- a/src/panel/tab-settings.js +++ b/src/panel/tab-settings.js @@ -1,5 +1,7 @@ export class SettingsTab { - render(container) { + render(container, configEntryId) { + const href = configEntryId ? `/config/integrations/integration/span_panel#config_entry=${configEntryId}` : "/config/integrations/integration/span_panel"; + container.innerHTML = `

Settings

@@ -7,7 +9,7 @@ export class SettingsTab { General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.

- Open SPAN Panel Integration Settings → From cbdb5dfa85e301fe8215cbe289b8e987e6bb4796 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:02:27 -0700 Subject: [PATCH 025/101] fix: monitoring enable/disable toggle and service call - Unchecking Enabled calls set_global_monitoring with enabled:false - Re-renders tab after enable/disable to reflect current state --- dist/span-panel.js | 2 +- src/panel/tab-monitoring.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index e7cd4fd..8608ec1 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",s="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const s=document.createElement("select");s.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),s.appendChild(e)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:s.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&s.setAttribute("checked",""),i.appendChild(o),i.appendChild(s),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,g=a?.window_duration_m??15,m=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(s),o.appendChild(a),o}_createDurationRow(t,e,n,i,o,s,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=s,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,s,a,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${v(y)}${b(y)}`;const T=l[g||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=L?d:"#555",F=``;let I="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);I=`${Math.round(t)}%`}const R=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${I}\n ${F}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return L(t,M)}function F(t){return L(t,z)}function I(t){return L(t,T)}function R(t){return L(t,N)}function A(t,e,n,i){const o=n.visible_sub_entities||{};let s="";if(!t.entities)return s;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return s}function P(t,e,n,i,o,s){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!o},{key:`${a}${t}_soe`,title:"SoE",available:!!s},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function H(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function O(t){return Math.max(500,Math.floor(t/5e3))}function W(t,e,n,i,o,s){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,s,a,c,l){const{options:d,series:p}=function(e,n,i,o,s){i||(i=r[t]);const a=o?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),s&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*s),h.push({type:"line",data:[[d,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,s,a){if(!t||!o||!e)return;const r=j(s);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],s=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(s))}!function(t,e,n,i,o){const s="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(s){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,s,c);const l=$(s),d="current"===l.entityRole;for(const[s,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${s}"]`);if(!o)continue;const p=c.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,g=c.device_type===i||h<0,m=c.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||c.relay_state)===n,_=o.querySelector(".power-value");if(_)if(d){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;_.innerHTML=`${l.format(i)}A`}else _.innerHTML=`${v(h)}${b(h)}`;const x=o.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=x.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",g);const w=o.querySelector(".chart-container");if(w){G(w,e,a.get(s)||[],r,l,g,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const s=j(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,s,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:s}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=F(i),t.soe=I(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${a}${n}_${i}`})}return e}(t))e.push(i),n.set(i,s)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),s=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(s.push(e),a.set(e,t))}if(V(e,s,a),0===s.length)return;o>72e5?await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,s,a,o,i):await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:s,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=H(i),c=O(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];o.set(i,D(e,r,c))}}}(t,s,a,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const a=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),s="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(a,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,s=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${s>0?`${s} alert${s>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,s){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);a.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=s?(o=s,a=e,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,s=a.get(e),u=a.get(n);if(p+=`
${e}
`,s&&"row-span"===s.layout){const{monInfo:e,sheddingPriority:a}=d(s);p+=C(s.uuid,s.circuit,t,"2 / 4","row-span",0,i,o,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(s);p+=C(s.uuid,s.circuit,t,"2",s.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(a,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===s&&!a)continue;const t=l.type===s?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?F(l):null,f=g?I(l):null,y=g?R(l):null,_=A(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(a,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,a),this._bindToggleClicks(t,a);try{await X(e,a,i,this._powerHistory)}catch{}B(t,e,a,i,this._powerHistory),U(t,e,a,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,a,this._config,this._powerHistory),U(t,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=H(t),n=O(t),i=Date.now(),o=i-t;for(const[t,s]of Object.entries(this._topology.circuits)){const a=S(s,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,s=e.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a})})}_bindGearClicks(t,e){t.addEventListener("click",n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!e)return;const a=e.circuits[s];if(!a)return;const r=this._monitoringCache?.status?.circuits?.[a.entities?.current||a.entities?.power]||null;o.open({...a,uuid:s,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";class Z{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},s=!0===i?.enabled,a=i?.circuits||{},r=i?.mains||{},c=[...Object.entries(a),...Object.entries(r)],l=c.map(([t,e])=>{const n=u(e.name||t),i=e.continuous_threshold_pct,o=e.spike_threshold_pct,s=e.window_duration_m,a=Object.prototype.hasOwnProperty.call(r,t);return`\n \n ${n}\n ${i??"--"}%\n ${o??"--"}%\n ${s??"--"}m\n \n \n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Per-Circuit Overrides

\n

\n Use Reset to Default to clear a custom override and restore the circuit to global defaults.\n

\n ${c.length>0?`\n \n \n \n \n \n \n \n \n \n \n ${l}\n
NameContinuousSpikeWindow
\n `:'\n

\n All circuits using global defaults. No per-circuit overrides are configured.\n

\n '}\n
\n `,this._bindGlobalControls(t,n),this._bindResetButtons(t,n)}_bindGlobalControls(t,n){const i=t.querySelector("#monitoring-enabled"),o=t.querySelector("#global-fields"),s=t.querySelector("#global-status"),a=async()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await n.callService(e,"set_global_monitoring",i),s.textContent="Saved",s.style.color="var(--success-color, #4caf50)",setTimeout(()=>{s.textContent=""},2e3)}catch(t){s.textContent=`Error: ${t.message||"Failed to save"}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const e=i.checked;o.style.opacity=e?"":"0.4",o.style.pointerEvents=e?"":"none",e&&(await a(),await this.render(t,n))});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,s=i.dataset.type,a="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===s?{leg:o}:{circuit_id:o};await n.callService(e,a,r),await this.render(t,n)})}}class tt{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class et extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new Z,this._settingsTab=new tt}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",et),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",s="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const s=document.createElement("select");s.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),s.appendChild(e)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:s.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&s.setAttribute("checked",""),i.appendChild(o),i.appendChild(s),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,g=a?.window_duration_m??15,m=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(s),o.appendChild(a),o}_createDurationRow(t,e,n,i,o,s,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=s,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,s,a,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${v(y)}${b(y)}`;const T=l[g||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=L?d:"#555",F=``;let I="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);I=`${Math.round(t)}%`}const R=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${I}\n ${F}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return L(t,M)}function F(t){return L(t,z)}function I(t){return L(t,T)}function R(t){return L(t,N)}function A(t,e,n,i){const o=n.visible_sub_entities||{};let s="";if(!t.entities)return s;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return s}function P(t,e,n,i,o,s){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!o},{key:`${a}${t}_soe`,title:"SoE",available:!!s},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function H(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function O(t){return Math.max(500,Math.floor(t/5e3))}function W(t,e,n,i,o,s){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,s,a,c,l){const{options:d,series:p}=function(e,n,i,o,s){i||(i=r[t]);const a=o?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),s&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*s),h.push({type:"line",data:[[d,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,s,a){if(!t||!o||!e)return;const r=j(s);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],s=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(s))}!function(t,e,n,i,o){const s="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(s){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,s,c);const l=$(s),d="current"===l.entityRole;for(const[s,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${s}"]`);if(!o)continue;const p=c.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,g=c.device_type===i||h<0,m=c.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||c.relay_state)===n,_=o.querySelector(".power-value");if(_)if(d){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;_.innerHTML=`${l.format(i)}A`}else _.innerHTML=`${v(h)}${b(h)}`;const x=o.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=x.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",g);const w=o.querySelector(".chart-container");if(w){G(w,e,a.get(s)||[],r,l,g,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const s=j(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,s,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:s}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=F(i),t.soe=I(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${a}${n}_${i}`})}return e}(t))e.push(i),n.set(i,s)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),s=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(s.push(e),a.set(e,t))}if(V(e,s,a),0===s.length)return;o>72e5?await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,s,a,o,i):await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:s,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=H(i),c=O(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];o.set(i,D(e,r,c))}}}(t,s,a,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const a=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),s="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(a,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,s=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${s>0?`${s} alert${s>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,s){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);a.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=s?(o=s,a=e,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,s=a.get(e),u=a.get(n);if(p+=`
${e}
`,s&&"row-span"===s.layout){const{monInfo:e,sheddingPriority:a}=d(s);p+=C(s.uuid,s.circuit,t,"2 / 4","row-span",0,i,o,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(s);p+=C(s.uuid,s.circuit,t,"2",s.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(a,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===s&&!a)continue;const t=l.type===s?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?F(l):null,f=g?I(l):null,y=g?R(l):null,_=A(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(a,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,a),this._bindToggleClicks(t,a);try{await X(e,a,i,this._powerHistory)}catch{}B(t,e,a,i,this._powerHistory),U(t,e,a,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,a,this._config,this._powerHistory),U(t,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=H(t),n=O(t),i=Date.now(),o=i-t;for(const[t,s]of Object.entries(this._topology.circuits)){const a=S(s,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,s=e.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a})})}_bindGearClicks(t,e){t.addEventListener("click",n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!e)return;const a=e.circuits[s];if(!a)return;const r=this._monitoringCache?.status?.circuits?.[a.entities?.current||a.entities?.power]||null;o.open({...a,uuid:s,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";class Z{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},s=!0===i?.enabled,a=i?.circuits||{},r=i?.mains||{},c=[...Object.entries(a),...Object.entries(r)],l=c.map(([t,e])=>{const n=u(e.name||t),i=e.continuous_threshold_pct,o=e.spike_threshold_pct,s=e.window_duration_m,a=Object.prototype.hasOwnProperty.call(r,t);return`\n \n ${n}\n ${i??"--"}%\n ${o??"--"}%\n ${s??"--"}m\n \n \n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Per-Circuit Overrides

\n

\n Use Reset to Default to clear a custom override and restore the circuit to global defaults.\n

\n ${c.length>0?`\n \n \n \n \n \n \n \n \n \n \n ${l}\n
NameContinuousSpikeWindow
\n `:'\n

\n All circuits using global defaults. No per-circuit overrides are configured.\n

\n '}\n
\n `,this._bindGlobalControls(t,n),this._bindResetButtons(t,n)}_bindGlobalControls(t,n){const i=t.querySelector("#monitoring-enabled"),o=t.querySelector("#global-fields"),s=t.querySelector("#global-status"),a=async()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await n.callService(e,"set_global_monitoring",i),s.textContent="Saved",s.style.color="var(--success-color, #4caf50)",setTimeout(()=>{s.textContent=""},2e3)}catch(t){s.textContent=`Error: ${t.message||"Failed to save"}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;if(o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none",s)await a();else try{await n.callService(e,"set_global_monitoring",{enabled:!1})}catch{}await this.render(t,n)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,s=i.dataset.type,a="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===s?{leg:o}:{circuit_id:o};await n.callService(e,a,r),await this.render(t,n)})}}class tt{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class et extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new Z,this._settingsTab=new tt}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",et),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index f87315f..50ba587 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -178,8 +178,14 @@ export class MonitoringTab { fieldsDiv.style.pointerEvents = enabled ? "" : "none"; if (enabled) { await saveGlobal(); - await this.render(container, hass); + } else { + try { + await hass.callService(INTEGRATION_DOMAIN, "set_global_monitoring", { enabled: false }); + } catch { + // ignore + } } + await this.render(container, hass); }); } From 61233c7635e2501f69a524a86825a5f12411222d Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:58:26 -0700 Subject: [PATCH 026/101] feat: monitoring tab UX improvements - All/None toggle checkbox above all monitored points - Mains and circuits in single unified table - Mains breaker shown as single 240V entry above circuits - Per-circuit and per-mains toggle checkboxes - Toggle-all applies to both mains and circuits --- dist/span-panel.js | 2 +- src/panel/tab-monitoring.js | 262 ++++++++++++++++++++++++++++-------- 2 files changed, 210 insertions(+), 54 deletions(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index 8608ec1..ed277eb 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",s="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const s=document.createElement("select");s.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),s.appendChild(e)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:s.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(s),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&s.setAttribute("checked",""),i.appendChild(o),i.appendChild(s),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,g=a?.window_duration_m??15,m=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),s.addEventListener("change",()=>{const t=s.hasAttribute("checked")||s.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(s),o.appendChild(a),o}_createDurationRow(t,e,n,i,o,s,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=s,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,s,a,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${v(y)}${b(y)}`;const T=l[g||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=L?d:"#555",F=``;let I="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);I=`${Math.round(t)}%`}const R=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${I}\n ${F}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return L(t,M)}function F(t){return L(t,z)}function I(t){return L(t,T)}function R(t){return L(t,N)}function A(t,e,n,i){const o=n.visible_sub_entities||{};let s="";if(!t.entities)return s;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return s}function P(t,e,n,i,o,s){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!o},{key:`${a}${t}_soe`,title:"SoE",available:!!s},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function H(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function O(t){return Math.max(500,Math.floor(t/5e3))}function W(t,e,n,i,o,s){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,s,a,c,l){const{options:d,series:p}=function(e,n,i,o,s){i||(i=r[t]);const a=o?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),s&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*s),h.push({type:"line",data:[[d,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,s,a){if(!t||!o||!e)return;const r=j(s);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],s=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(s))}!function(t,e,n,i,o){const s="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(s){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(s){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,s,c);const l=$(s),d="current"===l.entityRole;for(const[s,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${s}"]`);if(!o)continue;const p=c.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,g=c.device_type===i||h<0,m=c.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||c.relay_state)===n,_=o.querySelector(".power-value");if(_)if(d){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;_.innerHTML=`${l.format(i)}A`}else _.innerHTML=`${v(h)}${b(h)}`;const x=o.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=x.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",g);const w=o.querySelector(".chart-container");if(w){G(w,e,a.get(s)||[],r,l,g,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const s=j(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,s,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:s}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=F(i),t.soe=I(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${a}${n}_${i}`})}return e}(t))e.push(i),n.set(i,s)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),s=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(s.push(e),a.set(e,t))}if(V(e,s,a),0===s.length)return;o>72e5?await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,s,a,o,i):await async function(t,e,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:s,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=H(i),c=O(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const s=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&s.push({time:n,value:e})}if(s.length>0){const t=o.get(i)||[],e=[...s,...t];o.set(i,D(e,r,c))}}}(t,s,a,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const a=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),s="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(a,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,s=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${s>0?`${s} alert${s>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,s){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);a.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=s?(o=s,a=e,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,s=a.get(e),u=a.get(n);if(p+=`
${e}
`,s&&"row-span"===s.layout){const{monInfo:e,sheddingPriority:a}=d(s);p+=C(s.uuid,s.circuit,t,"2 / 4","row-span",0,i,o,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(s);p+=C(s.uuid,s.circuit,t,"2",s.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(a,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===s&&!a)continue;const t=l.type===s?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?F(l):null,f=g?I(l):null,y=g?R(l):null,_=A(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(a,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,a),this._bindToggleClicks(t,a);try{await X(e,a,i,this._powerHistory)}catch{}B(t,e,a,i,this._powerHistory),U(t,e,a,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,a,this._config,this._powerHistory),U(t,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=H(t),n=O(t),i=Date.now(),o=i-t;for(const[t,s]of Object.entries(this._topology.circuits)){const a=S(s,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,s=e.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a})})}_bindGearClicks(t,e){t.addEventListener("click",n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!e)return;const a=e.circuits[s];if(!a)return;const r=this._monitoringCache?.status?.circuits?.[a.entities?.current||a.entities?.power]||null;o.open({...a,uuid:s,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";class Z{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},s=!0===i?.enabled,a=i?.circuits||{},r=i?.mains||{},c=[...Object.entries(a),...Object.entries(r)],l=c.map(([t,e])=>{const n=u(e.name||t),i=e.continuous_threshold_pct,o=e.spike_threshold_pct,s=e.window_duration_m,a=Object.prototype.hasOwnProperty.call(r,t);return`\n \n ${n}\n ${i??"--"}%\n ${o??"--"}%\n ${s??"--"}m\n \n \n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Per-Circuit Overrides

\n

\n Use Reset to Default to clear a custom override and restore the circuit to global defaults.\n

\n ${c.length>0?`\n \n \n \n \n \n \n \n \n \n \n ${l}\n
NameContinuousSpikeWindow
\n `:'\n

\n All circuits using global defaults. No per-circuit overrides are configured.\n

\n '}\n
\n `,this._bindGlobalControls(t,n),this._bindResetButtons(t,n)}_bindGlobalControls(t,n){const i=t.querySelector("#monitoring-enabled"),o=t.querySelector("#global-fields"),s=t.querySelector("#global-status"),a=async()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await n.callService(e,"set_global_monitoring",i),s.textContent="Saved",s.style.color="var(--success-color, #4caf50)",setTimeout(()=>{s.textContent=""},2e3)}catch(t){s.textContent=`Error: ${t.message||"Failed to save"}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;if(o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none",s)await a();else try{await n.callService(e,"set_global_monitoring",{enabled:!1})}catch{}await this.render(t,n)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,s=i.dataset.type,a="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===s?{leg:o}:{circuit_id:o};await n.callService(e,a,r),await this.render(t,n)})}}class tt{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class et extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new Z,this._settingsTab=new tt}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",et),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},l={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},c={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(c).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=c[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=r?"block":"none",n.appendChild(l);const c=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,l.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=c?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),l.appendChild(p),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;l.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const l=document.createElement("div");l.className="field-row";const c=document.createElement("span");c.className="field-label",c.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),l.appendChild(c),l.appendChild(d),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,l,p,h,g){const m=e.entities?.power,f=m?l.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?l.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?l.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(y)}${b(y)}`;const z=c[g||"unknown"]||c.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=t.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(l)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,l,c){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",l=`rgb(${s})`,c=Date.now(),d=c-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:l}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(l||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let l=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(l+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=y(o)),r&&(r.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),c=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",c&&(c.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=y(t),c&&(c.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,l);const c=$(a),d="current"===c.entityRole;for(const[a,l]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const p=l.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.device_type===i||h<0,m=l.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||l.relay_state)===n,_=o.querySelector(".power-value");if(_)if(d){const t=l.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;_.innerHTML=`${c.format(i)}A`}else _.innerHTML=`${v(h)}${b(h)}`;const x=o.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=x.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",g);const w=o.querySelector(".chart-container");if(w){G(w,e,s.get(a)||[],r,c,g,o.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const t of c){const n=t.dataset.chartKey,i=o.get(n)||[];let s=l.power;n.endsWith("_soc")?s=l.soc:n.endsWith("_soe")&&(s=l.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),l=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,l))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),l=(j(i),this._monitoringCache.status),c=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(l),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const l=new Set,c=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?l.add(i):c.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!l.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!c.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,l),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[l,c]of Object.entries(t.sub_devices)){if(c.type===o&&!i)continue;if(c.type===a&&!s)continue;const t=c.type===a?"EV Charger":c.type===o?"Battery":"Sub-device",d=q(c),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=c.type===o,m=g?I(c):null,f=g?A(c):null,y=g?F(c):null,_=R(c,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(l,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(c.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${c}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s);try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=S(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(t)||[];c.length>0&&i-c[c.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;const r=this._monitoringCache?.status?.circuits?.[s.entities?.current||s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";class Z{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},a=!0===i?.enabled,s=i?.circuits||{},r=i?.mains||{},l=Object.entries(s).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),c=Object.entries(r),d=[...l,...c],p=d.length>0&&d.every(([,t])=>!1!==t.monitoring_enabled),h=d.some(([,t])=>!1!==t.monitoring_enabled),g=l.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=o?'Custom':"";return`\n \n \n \n \n ${e.continuous_threshold_pct??"--"}%\n ${e.spike_threshold_pct??"--"}%\n ${e.window_duration_m??"--"}m\n \n ${o?``:""}\n \n \n `}).join(""),m=Object.entries(r).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override;return`\n \n \n \n \n ${e.continuous_threshold_pct??"--"}%\n ${e.spike_threshold_pct??"--"}%\n ${e.window_duration_m??"--"}m\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${m}\n ${g}\n \n
NameContinuousSpikeWindow
\n \n
\n
\n `;const f=t.querySelector("#toggle-all-circuits");f&&!p&&h&&(f.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,s,r),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindResetButtons(t,n)}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:n})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),o.textContent="Saved",o.style.color="var(--success-color, #4caf50)",setTimeout(()=>{o.textContent=""},2e3)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:t,monitoring_enabled:s}}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:t,monitoring_enabled:s}}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:o,monitoring_enabled:a}})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:o,monitoring_enabled:a}})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:o}:{circuit_id:o};await n.callService(e,s,r),await this.render(t,n)})}}class tt{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class et extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new Z,this._settingsTab=new tt}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",et),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 50ba587..b46d987 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -38,28 +38,78 @@ export class MonitoringTab { const isEnabled = status?.enabled === true; const circuits = status?.circuits || {}; const mains = status?.mains || {}; - const allEntries = [...Object.entries(circuits), ...Object.entries(mains)]; - const monitoredRows = allEntries + const circuitEntries = Object.entries(circuits).sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || "")); + const mainsEntries = Object.entries(mains); + const allPoints = [...circuitEntries, ...mainsEntries]; + const allEnabled = allPoints.length > 0 && allPoints.every(([, c]) => c.monitoring_enabled !== false); + const someEnabled = allPoints.some(([, c]) => c.monitoring_enabled !== false); + + const circuitRows = circuitEntries + .map(([entityId, info]) => { + const name = escapeHtml(info.name || entityId); + const enabled = info.monitoring_enabled !== false; + const hasOverride = info.has_override === true; + const dimStyle = enabled ? "" : "opacity:0.4;"; + const badge = hasOverride + ? `Custom` + : ""; + return ` + + + + + ${info.continuous_threshold_pct ?? "--"}% + ${info.spike_threshold_pct ?? "--"}% + ${info.window_duration_m ?? "--"}m + + ${ + hasOverride + ? `` + : "" + } + + + `; + }) + .join(""); + + const mainsRows = Object.entries(mains) .map(([entityId, info]) => { const name = escapeHtml(info.name || entityId); - const continuous = info.continuous_threshold_pct; - const spike = info.spike_threshold_pct; - const window = info.window_duration_m; - const isMains = Object.prototype.hasOwnProperty.call(mains, entityId); + const enabled = info.monitoring_enabled !== false; + const hasOverride = info.has_override === true; + const dimStyle = enabled ? "" : "opacity:0.4;"; return ` - - ${name} - ${continuous ?? "--"}% - ${spike ?? "--"}% - ${window ?? "--"}m - - + + + + + ${info.continuous_threshold_pct ?? "--"}% + ${info.spike_threshold_pct ?? "--"}% + ${info.window_duration_m ?? "--"}m + + ${ + hasOverride + ? `` + : "" + } `; @@ -110,45 +160,63 @@ export class MonitoringTab {
-

Per-Circuit Overrides

-

- Use Reset to Default to clear a custom override and restore the circuit to global defaults. -

- ${ - allEntries.length > 0 - ? ` - - - - - - - - - - - ${monitoredRows} -
NameContinuousSpikeWindow
- ` - : ` -

- All circuits using global defaults. No per-circuit overrides are configured. -

- ` - } +

Monitored Points

+ + + + + + + + + + + + + + + ${mainsRows} + ${circuitRows} + +
NameContinuousSpikeWindow
+ +
`; + // Set indeterminate state on toggle-all checkbox + const toggleAllCb = container.querySelector("#toggle-all-circuits"); + if (toggleAllCb && !allEnabled && someEnabled) { + toggleAllCb.indeterminate = true; + } + this._bindGlobalControls(container, hass); + this._bindToggleAll(container, hass, circuits, mains); + this._bindCircuitToggles(container, hass); + this._bindMainsToggles(container, hass); this._bindResetButtons(container, hass); } + _callSetGlobal(hass, data) { + return hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "set_global_monitoring", + service_data: data, + }); + } + _bindGlobalControls(container, hass) { const enabledCheckbox = container.querySelector("#monitoring-enabled"); const fieldsDiv = container.querySelector("#global-fields"); const statusEl = container.querySelector("#global-status"); - const saveGlobal = async () => { + const saveGlobal = () => { clearTimeout(this._debounceTimer); this._debounceTimer = setTimeout(async () => { const data = { @@ -158,7 +226,7 @@ export class MonitoringTab { cooldown_duration_m: parseInt(container.querySelector("#g-cooldown").value, 10), }; try { - await hass.callService(INTEGRATION_DOMAIN, "set_global_monitoring", data); + await this._callSetGlobal(hass, data); statusEl.textContent = "Saved"; statusEl.style.color = "var(--success-color, #4caf50)"; setTimeout(() => { @@ -176,14 +244,25 @@ export class MonitoringTab { const enabled = enabledCheckbox.checked; fieldsDiv.style.opacity = enabled ? "" : "0.4"; fieldsDiv.style.pointerEvents = enabled ? "" : "none"; - if (enabled) { - await saveGlobal(); - } else { - try { - await hass.callService(INTEGRATION_DOMAIN, "set_global_monitoring", { enabled: false }); - } catch { - // ignore + const statusEl2 = container.querySelector("#global-status"); + try { + if (enabled) { + const data = { + continuous_threshold_pct: parseInt(container.querySelector("#g-continuous").value, 10), + spike_threshold_pct: parseInt(container.querySelector("#g-spike").value, 10), + window_duration_m: parseInt(container.querySelector("#g-window").value, 10), + cooldown_duration_m: parseInt(container.querySelector("#g-cooldown").value, 10), + }; + await this._callSetGlobal(hass, data); + } else { + await this._callSetGlobal(hass, { enabled: false }); } + } catch (err) { + if (statusEl2) { + statusEl2.textContent = `Error: ${err.message || "Failed"}`; + statusEl2.style.color = "var(--error-color, #f44336)"; + } + return; } await this.render(container, hass); }); @@ -194,6 +273,83 @@ export class MonitoringTab { } } + _bindToggleAll(container, hass, circuits, mains) { + const toggleAll = container.querySelector("#toggle-all-circuits"); + if (!toggleAll) return; + toggleAll.addEventListener("change", async () => { + const enabled = toggleAll.checked; + const calls = [ + ...Object.keys(circuits).map(entityId => + hass + .callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "set_circuit_threshold", + service_data: { circuit_id: entityId, monitoring_enabled: enabled }, + }) + .catch(() => {}) + ), + ...Object.keys(mains).map(entityId => + hass + .callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "set_mains_threshold", + service_data: { leg: entityId, monitoring_enabled: enabled }, + }) + .catch(() => {}) + ), + ]; + await Promise.all(calls); + await this.render(container, hass); + }); + } + + _bindMainsToggles(container, hass) { + for (const cb of container.querySelectorAll(".mains-toggle")) { + cb.addEventListener("change", async () => { + const entityId = cb.dataset.entity; + const enabled = cb.checked; + try { + // Set both upstream legs together (single 240V breaker) + await Promise.all([ + hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "set_mains_threshold", + service_data: { leg: entityId, monitoring_enabled: enabled }, + }), + ]); + } catch { + cb.checked = !enabled; + return; + } + await this.render(container, hass); + }); + } + } + + _bindCircuitToggles(container, hass) { + for (const cb of container.querySelectorAll(".circuit-toggle")) { + cb.addEventListener("change", async () => { + const entityId = cb.dataset.entity; + const enabled = cb.checked; + try { + await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "set_circuit_threshold", + service_data: { circuit_id: entityId, monitoring_enabled: enabled }, + }); + } catch { + cb.checked = !enabled; + return; + } + await this.render(container, hass); + }); + } + } + _bindResetButtons(container, hass) { for (const btn of container.querySelectorAll(".reset-btn")) { btn.addEventListener("click", async () => { From d64566e98a88f9b83a9834e53795863d71e30dac Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:07:57 -0700 Subject: [PATCH 027/101] fix: sidebar monitoring toggle and monitoring tab improvements - Fix ha-switch toggle reading hasAttribute instead of .checked property, causing monitoring_enabled to always send true - Check monitoring_enabled field when rendering initial toggle state - Add cache invalidation on side-panel close for fresh data on reopen - Dispatch side-panel-closed event from close() method - Use entity power key consistently for monitoring info lookup - Add inline threshold editing in monitoring tab with debounced saves - Update shedding priority icons on DOM refresh - Use has_override field for override count in summary bar --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/span-panel-card.js | 2 +- src/core/dom-updater.js | 14 +++++- src/core/monitoring-status.js | 7 ++- src/core/side-panel.js | 18 ++++---- src/panel/tab-dashboard.js | 9 +++- src/panel/tab-monitoring.js | 81 ++++++++++++++++++++++++++++------- 8 files changed, 104 insertions(+), 31 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index c01aa57..890b1ec 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const y=r.power;function b(t){return y.unit(t)}function x(t){return(t<0?"-":"")+y.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,y=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${b(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function F(t){return P(t,N)}function W(t){return P(t,L)}function D(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function I(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=F(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type!==i&&(c+=Math.abs(o))}!function(t,e,n,i,s){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=w(s)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=w(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=w(t)}else u.textContent="--";h&&(h.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?i.state:"--"}}(t,e,s,o,c);const l=_(o),d="current"===l.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const p=c.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,f=c.device_type===i||h<0,g=c.entities?.switch,m=g?e.states[g]:null,_=m?"on"===m.state:(u?.attributes?.relay_state||c.relay_state)===n,v=s.querySelector(".power-value");if(v)if(d){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;v.innerHTML=`${l.format(i)}A`}else v.innerHTML=`${x(h)}${b(h)}`;const y=s.querySelector(".toggle-pill");if(y){y.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=y.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}s.classList.toggle("circuit-off",!_),s.classList.toggle("circuit-producer",f);const w=s.querySelector(".chart-container");if(w){j(w,e,a.get(o)||[],r,l,f,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("button");s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e,!0)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;c.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.current||s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?F(l):null,_=f?W(l):null,v=D(l,e,n,new Set([d,g,m,_].filter(Boolean))),y=I(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${b(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const b=r.power;function y(t){return b.unit(t)}function x(t){return(t<0?"-":"")+b.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,b=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${y(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function F(t){return P(t,N)}function W(t){return P(t,L)}function D(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function I(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=F(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type!==i&&(c+=Math.abs(o))}!function(t,e,n,i,s){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=w(s)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=w(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=w(t)}else u.textContent="--";h&&(h.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?i.state:"--"}}(t,e,s,o,c);const d=_(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const u=c.entities?.power,h=u?e.states[u]:null,f=h&&parseFloat(h.state)||0,g=c.device_type===i||f<0,m=c.entities?.switch,_=m?e.states[m]:null,v=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,b=s.querySelector(".power-value");if(b)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;b.innerHTML=`${d.format(i)}A`}else b.innerHTML=`${x(f)}${y(f)}`;const w=s.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(v?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=v?"On":"Off")}s.classList.toggle("circuit-off",!v),s.classList.toggle("circuit-producer",g);const C=c.entities?.select,k=C?e.states[C]:null,S=k?k.state:"unknown",$=l[S]||l.unknown,E=s.querySelector(".shedding-icon");E&&(E.setAttribute("icon",$.icon),E.style.color=$.color,E.title=$.label);const M=s.querySelector(".chart-container");if(M){j(M,e,a.get(o)||[],r,d,g,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("button");s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a&&!1!==a.monitoring_enabled;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e,!0)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${y(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?F(l):null,_=f?W(l):null,v=D(l,e,n,new Set([d,g,m,_].filter(Boolean))),b=I(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${y(h)}`:""}\n
\n ${b}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index ed277eb..85777ae 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},l={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},c={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(c).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=c[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=r?"block":"none",n.appendChild(l);const c=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,l.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=c?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),l.appendChild(p),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;l.style.display=t?"block":"none",t||this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;p.style.display=n?"block":"none",!n&&t.checked&&this._callDomainService("clear_circuit_threshold",{circuit_id:e.uuid}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const l=document.createElement("div");l.className="field-row";const c=document.createElement("span");c.className="field-label",c.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),l.appendChild(c),l.appendChild(d),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,l,p,h,g){const m=e.entities?.power,f=m?l.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?l.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?l.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(y)}${b(y)}`;const z=c[g||"unknown"]||c.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=t.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(l)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,l,c){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",l=`rgb(${s})`,c=Date.now(),d=c-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:l}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(l||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let l=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(l+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=y(o)),r&&(r.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),c=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",c&&(c.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=y(t),c&&(c.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,l);const c=$(a),d="current"===c.entityRole;for(const[a,l]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const p=l.entities?.power,u=p?e.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.device_type===i||h<0,m=l.entities?.switch,f=m?e.states[m]:null,y=f?"on"===f.state:(u?.attributes?.relay_state||l.relay_state)===n,_=o.querySelector(".power-value");if(_)if(d){const t=l.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;_.innerHTML=`${c.format(i)}A`}else _.innerHTML=`${v(h)}${b(h)}`;const x=o.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=x.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",g);const w=o.querySelector(".chart-container");if(w){G(w,e,s.get(a)||[],r,c,g,o.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const t of c){const n=t.dataset.chartKey,i=o.get(n)||[];let s=l.power;n.endsWith("_soc")?s=l.soc:n.endsWith("_soe")&&(s=l.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),l=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,l))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),l=(j(i),this._monitoringCache.status),c=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>void 0!==t.continuous_threshold_pct).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(l),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const l=new Set,c=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?l.add(i):c.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!l.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!c.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,l),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[l,c]of Object.entries(t.sub_devices)){if(c.type===o&&!i)continue;if(c.type===a&&!s)continue;const t=c.type===a?"EV Charger":c.type===o?"Battery":"Sub-device",d=q(c),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=c.type===o,m=g?I(c):null,f=g?A(c):null,y=g?F(c):null,_=R(c,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(l,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(c.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${c}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s);try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=S(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(t)||[];c.length>0&&i-c[c.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;const r=this._monitoringCache?.status?.circuits?.[s.entities?.current||s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";class Z{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},a=!0===i?.enabled,s=i?.circuits||{},r=i?.mains||{},l=Object.entries(s).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),c=Object.entries(r),d=[...l,...c],p=d.length>0&&d.every(([,t])=>!1!==t.monitoring_enabled),h=d.some(([,t])=>!1!==t.monitoring_enabled),g=l.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=o?'Custom':"";return`\n \n \n \n \n ${e.continuous_threshold_pct??"--"}%\n ${e.spike_threshold_pct??"--"}%\n ${e.window_duration_m??"--"}m\n \n ${o?``:""}\n \n \n `}).join(""),m=Object.entries(r).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override;return`\n \n \n \n \n ${e.continuous_threshold_pct??"--"}%\n ${e.spike_threshold_pct??"--"}%\n ${e.window_duration_m??"--"}m\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${m}\n ${g}\n \n
NameContinuousSpikeWindow
\n \n
\n
\n `;const f=t.querySelector("#toggle-all-circuits");f&&!p&&h&&(f.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,s,r),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindResetButtons(t,n)}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:n})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),o.textContent="Saved",o.style.color="var(--success-color, #4caf50)",setTimeout(()=>{o.textContent=""},2e3)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:t,monitoring_enabled:s}}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:t,monitoring_enabled:s}}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:o,monitoring_enabled:a}})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:o,monitoring_enabled:a}})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:o}:{circuit_id:o};await n.callService(e,s,r),await this.render(t,n)})}}class tt{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class et extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new Z,this._settingsTab=new tt}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",et),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(y)}${b(y)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,y=f?e.states[f]:null,_=y?"on"===y.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}o.classList.toggle("circuit-off",!_),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=l[S]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,y=g?F(l):null,_=R(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},a=!0===i?.enabled,s=i?.circuits||{},r=i?.mains||{},c=Object.entries(s).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),l=Object.entries(r),d=[...c,...l],p=d.length>0&&d.every(([,t])=>!1!==t.monitoring_enabled),h=d.some(([,t])=>!1!==t.monitoring_enabled),g=c.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),m=Object.entries(r).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${m}\n ${g}\n \n
NameContinuousSpikeWindow
\n \n
\n
\n `;const f=t.querySelector("#toggle-all-circuits");f&&!p&&h&&(f.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,s,r),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:n})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),o.textContent="Saved",o.style.color="var(--success-color, #4caf50)",setTimeout(()=>{o.textContent=""},2e3)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:t,monitoring_enabled:s}}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:t,monitoring_enabled:s}}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:o,monitoring_enabled:a}})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:o,monitoring_enabled:a}})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:{[l]:a,[s]:i}}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:o}:{circuit_id:o};await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 0de7e36..78730ba 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -254,7 +254,7 @@ export class SpanPanelCard extends HTMLElement { const circuit = this._topology.circuits[uuid]; if (!circuit) return; - const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.current || circuit.entities?.power] || null; + const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; sidePanel.open({ ...circuit, diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index fda40e9..fbad81f 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -1,4 +1,4 @@ -import { BESS_CHART_METRICS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED } from "../constants.js"; +import { BESS_CHART_METRICS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES } from "../constants.js"; import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; import { getChartMetric } from "../helpers/chart.js"; import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; @@ -159,6 +159,18 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory) { slot.classList.toggle("circuit-off", !isOn); slot.classList.toggle("circuit-producer", isProducer); + // Update shedding priority icon + const selectEid = circuit.entities?.select; + const selectState = selectEid ? hass.states[selectEid] : null; + const priority = selectState ? selectState.state : "unknown"; + const shedInfo = SHEDDING_PRIORITIES[priority] || SHEDDING_PRIORITIES.unknown; + const sheddingIcon = slot.querySelector(".shedding-icon"); + if (sheddingIcon) { + sheddingIcon.setAttribute("icon", shedInfo.icon); + sheddingIcon.style.color = shedInfo.color; + sheddingIcon.title = shedInfo.label; + } + const chartContainer = slot.querySelector(".chart-container"); if (chartContainer) { const history = powerHistory.get(uuid) || []; diff --git a/src/core/monitoring-status.js b/src/core/monitoring-status.js index 99592df..ea8ae50 100644 --- a/src/core/monitoring-status.js +++ b/src/core/monitoring-status.js @@ -45,6 +45,11 @@ export class MonitoringStatusCache { return this._status; } + /** Force the next fetch() call to re-query the backend. */ + invalidate() { + this._lastFetch = 0; + } + /** @returns {object|null} Last fetched status */ get status() { return this._status; @@ -126,7 +131,7 @@ export function buildMonitoringSummaryHTML(status) { const warnings = all.filter(p => p.utilization_pct >= 80 && p.utilization_pct < 100).length; const alerts = all.filter(p => p.utilization_pct >= 100).length; - const overrides = all.filter(p => p.continuous_threshold_pct !== undefined).length; + const overrides = all.filter(p => p.has_override).length; return `
diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 6aee680..4a1a503 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -202,6 +202,7 @@ class SpanSidePanel extends HTMLElement { close() { this.removeAttribute("open"); this._config = null; + this.dispatchEvent(new CustomEvent("side-panel-closed", { bubbles: true, composed: true })); } // ── Rendering ───────────────────────────────────────────────────────── @@ -407,7 +408,7 @@ class SpanSidePanel extends HTMLElement { enableToggle.dataset.role = "monitoring-toggle"; const info = cfg.monitoringInfo; - const isEnabled = info != null; + const isEnabled = info != null && info.monitoring_enabled !== false; if (isEnabled) { enableToggle.setAttribute("checked", ""); } @@ -450,13 +451,13 @@ class SpanSidePanel extends HTMLElement { // Event: monitoring enable toggle enableToggle.addEventListener("change", () => { - const checked = enableToggle.hasAttribute("checked") || enableToggle.checked; + const checked = enableToggle.checked; detailsWrap.style.display = checked ? "block" : "none"; - if (!checked) { - this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }).catch(err => - this._showError(`Clear monitoring failed: ${err.message ?? err}`) - ); - } + const entityId = cfg.entities?.power || cfg.uuid; + this._callDomainService("set_circuit_threshold", { + circuit_id: entityId, + monitoring_enabled: checked, + }).catch(err => this._showError(`Monitoring toggle failed: ${err.message ?? err}`)); }); // Event: radio change @@ -466,7 +467,8 @@ class SpanSidePanel extends HTMLElement { const isCustom = radio.value === "custom" && radio.checked; thresholdsWrap.style.display = isCustom ? "block" : "none"; if (!isCustom && radio.checked) { - this._callDomainService("clear_circuit_threshold", { circuit_id: cfg.uuid }).catch(err => + const entityId = cfg.entities?.power || cfg.uuid; + this._callDomainService("clear_circuit_threshold", { circuit_id: entityId }).catch(err => this._showError(`Clear monitoring failed: ${err.message ?? err}`) ); } diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 8bd26f1..a781fb1 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -67,6 +67,9 @@ export class DashboardTab { this._bindGearClicks(container, topo); this._bindToggleClicks(container, topo); + container.addEventListener("side-panel-closed", () => { + this._monitoringCache.invalidate(); + }); try { await loadHistory(hass, topo, config, this._powerHistory); @@ -130,7 +133,7 @@ export class DashboardTab { } _bindGearClicks(container, topology) { - container.addEventListener("click", e => { + container.addEventListener("click", async e => { const gearBtn = e.target.closest(".gear-icon"); if (!gearBtn) return; @@ -149,7 +152,9 @@ export class DashboardTab { const circuit = topology.circuits[uuid]; if (!circuit) return; - const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.current || circuit.entities?.power] || null; + // Always fetch fresh monitoring data before opening side panel + await this._monitoringCache.fetch(this._hass); + const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; sidePanel.open({ ...circuit, diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index b46d987..f1cefca 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -13,6 +13,21 @@ const INPUT_STYLE = ` const LABEL_STYLE = ` min-width:130px;font-size:0.85em;color:var(--secondary-text-color); `; +const CELL_INPUT_STYLE = ` + background:var(--secondary-background-color,#333); + border:1px solid var(--divider-color); + color:var(--primary-text-color); + border-radius:3px;padding:3px 6px;width:50px;font-size:0.8em; + text-align:center; +`; + +function thresholdCell(entityId, field, value, unit, type) { + return ` + ${unit} + `; +} export class MonitoringTab { constructor() { @@ -51,26 +66,24 @@ export class MonitoringTab { const enabled = info.monitoring_enabled !== false; const hasOverride = info.has_override === true; const dimStyle = enabled ? "" : "opacity:0.4;"; - const badge = hasOverride - ? `Custom` - : ""; + const eid = escapeHtml(entityId); return ` - ${info.continuous_threshold_pct ?? "--"}% - ${info.spike_threshold_pct ?? "--"}% - ${info.window_duration_m ?? "--"}m - + ${thresholdCell(eid, "continuous_threshold_pct", info.continuous_threshold_pct, "%", "circuit")} + ${thresholdCell(eid, "spike_threshold_pct", info.spike_threshold_pct, "%", "circuit")} + ${thresholdCell(eid, "window_duration_m", info.window_duration_m, "m", "circuit")} + ${ hasOverride - ? `` @@ -88,23 +101,24 @@ export class MonitoringTab { const enabled = info.monitoring_enabled !== false; const hasOverride = info.has_override === true; const dimStyle = enabled ? "" : "opacity:0.4;"; + const eid = escapeHtml(entityId); return ` - ${info.continuous_threshold_pct ?? "--"}% - ${info.spike_threshold_pct ?? "--"}% - ${info.window_duration_m ?? "--"}m - + ${thresholdCell(eid, "continuous_threshold_pct", info.continuous_threshold_pct, "%", "mains")} + ${thresholdCell(eid, "spike_threshold_pct", info.spike_threshold_pct, "%", "mains")} + ${thresholdCell(eid, "window_duration_m", info.window_duration_m, "m", "mains")} + ${ hasOverride - ? `` @@ -199,6 +213,7 @@ export class MonitoringTab { this._bindToggleAll(container, hass, circuits, mains); this._bindCircuitToggles(container, hass); this._bindMainsToggles(container, hass); + this._bindThresholdInputs(container, hass); this._bindResetButtons(container, hass); } @@ -350,6 +365,40 @@ export class MonitoringTab { } } + _bindThresholdInputs(container, hass) { + const timers = new Map(); + for (const input of container.querySelectorAll(".threshold-input")) { + input.addEventListener("input", () => { + const key = `${input.dataset.entity}-${input.dataset.field}`; + clearTimeout(timers.get(key)); + timers.set( + key, + setTimeout(async () => { + const val = parseInt(input.value, 10); + if (!val || val < 1) return; + const entityId = input.dataset.entity; + const field = input.dataset.field; + const type = input.dataset.type; + const service = type === "mains" ? "set_mains_threshold" : "set_circuit_threshold"; + const idField = type === "mains" ? "leg" : "circuit_id"; + try { + await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service, + service_data: { [idField]: entityId, [field]: val }, + }); + // Re-render to update Custom badge and Reset button + await this.render(container, hass); + } catch { + input.style.borderColor = "var(--error-color, #f44336)"; + } + }, 800) + ); + }); + } + } + _bindResetButtons(container, hass) { for (const btn of container.querySelectorAll(".reset-btn")) { btn.addEventListener("click", async () => { From 74ceee781c6e9be2a6f1dfe3529215ae930bf6ab Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:19:03 -0700 Subject: [PATCH 028/101] fix: re-render monitoring tab after saving global settings After saving global threshold changes, re-render the full table so circuits without overrides immediately reflect the new values. --- dist/span-panel.js | 2 +- src/panel/tab-monitoring.js | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index 85777ae..e6e1fc7 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(y)}${b(y)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,y=f?e.states[f]:null,_=y?"on"===y.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}o.classList.toggle("circuit-off",!_),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=l[S]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,y=g?F(l):null,_=R(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},a=!0===i?.enabled,s=i?.circuits||{},r=i?.mains||{},c=Object.entries(s).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),l=Object.entries(r),d=[...c,...l],p=d.length>0&&d.every(([,t])=>!1!==t.monitoring_enabled),h=d.some(([,t])=>!1!==t.monitoring_enabled),g=c.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),m=Object.entries(r).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${m}\n ${g}\n \n
NameContinuousSpikeWindow
\n \n
\n
\n `;const f=t.querySelector("#toggle-all-circuits");f&&!p&&h&&(f.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,s,r),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:n})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),o.textContent="Saved",o.style.color="var(--success-color, #4caf50)",setTimeout(()=>{o.textContent=""},2e3)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:t,monitoring_enabled:s}}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:t,monitoring_enabled:s}}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:o,monitoring_enabled:a}})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:o,monitoring_enabled:a}})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:{[l]:a,[s]:i}}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:o}:{circuit_id:o};await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(y)}${b(y)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,y=f?e.states[f]:null,_=y?"on"===y.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}o.classList.toggle("circuit-off",!_),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=l[S]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,y=g?F(l):null,_=R(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},a=!0===i?.enabled,s=i?.circuits||{},r=i?.mains||{},c=Object.entries(s).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),l=Object.entries(r),d=[...c,...l],p=d.length>0&&d.every(([,t])=>!1!==t.monitoring_enabled),h=d.some(([,t])=>!1!==t.monitoring_enabled),g=c.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),m=Object.entries(r).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${m}\n ${g}\n \n
NameContinuousSpikeWindow
\n \n
\n
\n `;const f=t.querySelector("#toggle-all-circuits");f&&!p&&h&&(f.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,s,r),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:n})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:t,monitoring_enabled:s}}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:t,monitoring_enabled:s}}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:o,monitoring_enabled:a}})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:o,monitoring_enabled:a}})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:{[l]:a,[s]:i}}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:o}:{circuit_id:o};await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index f1cefca..1902ee9 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -242,11 +242,7 @@ export class MonitoringTab { }; try { await this._callSetGlobal(hass, data); - statusEl.textContent = "Saved"; - statusEl.style.color = "var(--success-color, #4caf50)"; - setTimeout(() => { - statusEl.textContent = ""; - }, 2000); + await this.render(container, hass); } catch (err) { statusEl.textContent = `Error: ${err.message || "Failed to save"}`; statusEl.style.color = "var(--error-color, #f44336)"; From 8f99b1b3613ab2b1595d11f3bd266d4450d2319a Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:33:01 -0700 Subject: [PATCH 029/101] feat: multi-panel monitoring and cooldown column Filter BESS/EVSE sub-devices from panel selector by excluding devices with via_device_id. Pass config_entry_id through all monitoring tab service calls so the correct panel's monitor is targeted. Add cooldown column to the monitored points table. --- dist/span-panel.js | 2 +- src/panel/span-panel.js | 9 ++++++--- src/panel/tab-monitoring.js | 30 +++++++++++++++++++++--------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index e6e1fc7..d381149 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function y(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,y=f&&parseFloat(f.state)||0,_=e.device_type===i||y<0,x=e.entities?.switch,w=x?c.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(y)}${b(y)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=y(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=y(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=y(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=y(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,y=f?e.states[f]:null,_=y?"on"===y.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}o.classList.toggle("circuit-off",!_),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=l[S]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,y=g?F(l):null,_=R(l,e,n,new Set([d,m,f,y].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null}async render(t,n){let i;try{const t=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});i=t?.response||null}catch{i=null}const o=i?.global_settings||{},a=!0===i?.enabled,s=i?.circuits||{},r=i?.mains||{},c=Object.entries(s).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),l=Object.entries(r),d=[...c,...l],p=d.length>0&&d.every(([,t])=>!1!==t.monitoring_enabled),h=d.some(([,t])=>!1!==t.monitoring_enabled),g=c.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),m=Object.entries(r).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${m}\n ${g}\n \n
NameContinuousSpikeWindow
\n \n
\n
\n `;const f=t.querySelector("#toggle-all-circuits");f&&!p&&h&&(f.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,s,r),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:n})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:t,monitoring_enabled:s}}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:t,monitoring_enabled:s}}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:{leg:o,monitoring_enabled:a}})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:{circuit_id:o,monitoring_enabled:a}})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:{[l]:a,[s]:i}}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r="mains"===a?{leg:o}:{circuit_id:o};await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e));const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":t.innerHTML="",await this._monitoringTab.render(t,this._hass);break;case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function _(t){return(Math.abs(t)/1e3).toFixed(1)}function y(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return y(e)===y(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,_=f&&parseFloat(f.state)||0,y=e.device_type===i||_<0,x=e.entities?.switch,w=x?c.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(_)}${b(_)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function P(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function R(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=_(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=_(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=_(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=_(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,_=f?e.states[f]:null,y=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=l[S]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=y(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,_=g?F(l):null,y=P(l,e,n,new Set([d,m,f,_].filter(Boolean))),x=R(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${y}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(t,n,i){let o;void 0!==i&&(this._configEntryId=i);try{const t={};this._configEntryId&&(t.config_entry_id=this._configEntryId);const i=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:t,return_response:!0});o=i?.response||null}catch{o=null}const a=o?.global_settings||{},s=!0===o?.enabled,r=o?.circuits||{},c=o?.mains||{},l=Object.entries(r).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),d=Object.entries(c),p=[...l,...d],h=p.length>0&&p.every(([,t])=>!1!==t.monitoring_enabled),g=p.some(([,t])=>!1!==t.monitoring_enabled),m=l.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),f=Object.entries(c).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${f}\n ${m}\n \n
NameContinuousSpikeWindowCooldown
\n \n
\n
\n `;const b=t.querySelector("#toggle-all-circuits");b&&!h&&g&&(b.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,r,c),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_serviceData(t){return this._configEntryId&&(t.config_entry_id=this._configEntryId),t}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:this._serviceData({...n})})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:t,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:t,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:o,monitoring_enabled:a})})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:o,monitoring_enabled:a})})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:this._serviceData({[l]:a,[s]:i})}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:o}:{circuit_id:o});await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e)&&!t.via_device_id);const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;await this._monitoringTab.render(t,this._hass,n);break}case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 424b61a..9550bdd 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -89,7 +89,7 @@ export class SpanPanelElement extends HTMLElement { const devices = await this._hass.callWS({ type: "config/device_registry/list", }); - this._panels = devices.filter(d => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN)); + this._panels = devices.filter(d => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN) && !d.via_device_id); const stored = localStorage.getItem("span_panel_selected"); if (stored && this._panels.some(p => p.id === stored)) { @@ -213,10 +213,13 @@ export class SpanPanelElement extends HTMLElement { await this._dashboardTab.render(container, this._hass, this._selectedPanelId, config); break; } - case "monitoring": + case "monitoring": { container.innerHTML = ""; - await this._monitoringTab.render(container, this._hass); + const monDevice = this._panels.find(p => p.id === this._selectedPanelId); + const monEntryId = monDevice?.config_entries?.[0] || null; + await this._monitoringTab.render(container, this._hass, monEntryId); break; + } case "settings": { container.innerHTML = ""; const selectedDevice = this._panels.find(p => p.id === this._selectedPanelId); diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 1902ee9..67ebcd6 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -32,16 +32,20 @@ function thresholdCell(entityId, field, value, unit, type) { export class MonitoringTab { constructor() { this._debounceTimer = null; + this._configEntryId = null; } - async render(container, hass) { + async render(container, hass, configEntryId) { + if (configEntryId !== undefined) this._configEntryId = configEntryId; let status; try { + const serviceData = {}; + if (this._configEntryId) serviceData.config_entry_id = this._configEntryId; const resp = await hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, service: "get_monitoring_status", - service_data: {}, + service_data: serviceData, return_response: true, }); status = resp?.response || null; @@ -80,6 +84,7 @@ export class MonitoringTab { ${thresholdCell(eid, "continuous_threshold_pct", info.continuous_threshold_pct, "%", "circuit")} ${thresholdCell(eid, "spike_threshold_pct", info.spike_threshold_pct, "%", "circuit")} ${thresholdCell(eid, "window_duration_m", info.window_duration_m, "m", "circuit")} + ${thresholdCell(eid, "cooldown_duration_m", info.cooldown_duration_m, "m", "circuit")} ${ hasOverride @@ -115,6 +120,7 @@ export class MonitoringTab { ${thresholdCell(eid, "continuous_threshold_pct", info.continuous_threshold_pct, "%", "mains")} ${thresholdCell(eid, "spike_threshold_pct", info.spike_threshold_pct, "%", "mains")} ${thresholdCell(eid, "window_duration_m", info.window_duration_m, "m", "mains")} + ${thresholdCell(eid, "cooldown_duration_m", info.cooldown_duration_m, "m", "mains")} ${ hasOverride @@ -182,6 +188,7 @@ export class MonitoringTab { Continuous Spike Window + Cooldown @@ -217,12 +224,17 @@ export class MonitoringTab { this._bindResetButtons(container, hass); } + _serviceData(data) { + if (this._configEntryId) data.config_entry_id = this._configEntryId; + return data; + } + _callSetGlobal(hass, data) { return hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, service: "set_global_monitoring", - service_data: data, + service_data: this._serviceData({ ...data }), }); } @@ -296,7 +308,7 @@ export class MonitoringTab { type: "call_service", domain: INTEGRATION_DOMAIN, service: "set_circuit_threshold", - service_data: { circuit_id: entityId, monitoring_enabled: enabled }, + service_data: this._serviceData({ circuit_id: entityId, monitoring_enabled: enabled }), }) .catch(() => {}) ), @@ -306,7 +318,7 @@ export class MonitoringTab { type: "call_service", domain: INTEGRATION_DOMAIN, service: "set_mains_threshold", - service_data: { leg: entityId, monitoring_enabled: enabled }, + service_data: this._serviceData({ leg: entityId, monitoring_enabled: enabled }), }) .catch(() => {}) ), @@ -328,7 +340,7 @@ export class MonitoringTab { type: "call_service", domain: INTEGRATION_DOMAIN, service: "set_mains_threshold", - service_data: { leg: entityId, monitoring_enabled: enabled }, + service_data: this._serviceData({ leg: entityId, monitoring_enabled: enabled }), }), ]); } catch { @@ -350,7 +362,7 @@ export class MonitoringTab { type: "call_service", domain: INTEGRATION_DOMAIN, service: "set_circuit_threshold", - service_data: { circuit_id: entityId, monitoring_enabled: enabled }, + service_data: this._serviceData({ circuit_id: entityId, monitoring_enabled: enabled }), }); } catch { cb.checked = !enabled; @@ -382,7 +394,7 @@ export class MonitoringTab { type: "call_service", domain: INTEGRATION_DOMAIN, service, - service_data: { [idField]: entityId, [field]: val }, + service_data: this._serviceData({ [idField]: entityId, [field]: val }), }); // Re-render to update Custom badge and Reset button await this.render(container, hass); @@ -401,7 +413,7 @@ export class MonitoringTab { const entityId = btn.dataset.entity; const type = btn.dataset.type; const service = type === "mains" ? "clear_mains_threshold" : "clear_circuit_threshold"; - const param = type === "mains" ? { leg: entityId } : { circuit_id: entityId }; + const param = this._serviceData(type === "mains" ? { leg: entityId } : { circuit_id: entityId }); await hass.callService(INTEGRATION_DOMAIN, service, param); await this.render(container, hass); }); From 48e397e8ac4298dff287d00635661556a55a5ed3 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:39:42 -0700 Subject: [PATCH 030/101] fix: sidebar cooldown editable and grid state translation Remove readOnly flag from cooldown duration row in sidebar. Include cooldown_duration_m in both threshold save handlers and use entity ID consistently for circuit_id. Use hass.formatEntityState for DSM grid state display. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/core/dom-updater.js | 2 +- src/core/side-panel.js | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 890b1ec..4a82c0b 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const b=r.power;function y(t){return b.unit(t)}function x(t){return(t<0?"-":"")+b.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,b=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${y(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function F(t){return P(t,N)}function W(t){return P(t,L)}function D(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function I(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=F(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type!==i&&(c+=Math.abs(o))}!function(t,e,n,i,s){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=w(s)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=w(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=w(t)}else u.textContent="--";h&&(h.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?i.state:"--"}}(t,e,s,o,c);const d=_(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const u=c.entities?.power,h=u?e.states[u]:null,f=h&&parseFloat(h.state)||0,g=c.device_type===i||f<0,m=c.entities?.switch,_=m?e.states[m]:null,v=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,b=s.querySelector(".power-value");if(b)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;b.innerHTML=`${d.format(i)}A`}else b.innerHTML=`${x(f)}${y(f)}`;const w=s.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(v?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=v?"On":"Off")}s.classList.toggle("circuit-off",!v),s.classList.toggle("circuit-producer",g);const C=c.entities?.select,k=C?e.states[C]:null,S=k?k.state:"unknown",$=l[S]||l.unknown,E=s.querySelector(".shedding-icon");E&&(E.setAttribute("icon",$.icon),E.style.color=$.color,E.title=$.label);const M=s.querySelector(".chart-container");if(M){j(M,e,a.get(o)||[],r,d,g,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("button");s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a&&!1!==a.monitoring_enabled;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e,!0)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${y(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?F(l):null,_=f?W(l):null,v=D(l,e,n,new Set([d,g,m,_].filter(Boolean))),b=I(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${y(h)}`:""}\n
\n ${b}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const y=r.power;function b(t){return y.unit(t)}function x(t){return(t<0?"-":"")+y.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,y=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${b(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function F(t){return P(t,N)}function W(t){return P(t,L)}function D(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function I(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=F(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type!==i&&(c+=Math.abs(o))}!function(t,e,n,i,s){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=w(s)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=w(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=w(t)}else u.textContent="--";h&&(h.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(t,e,s,o,c);const d=_(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const u=c.entities?.power,h=u?e.states[u]:null,f=h&&parseFloat(h.state)||0,g=c.device_type===i||f<0,m=c.entities?.switch,_=m?e.states[m]:null,v=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,y=s.querySelector(".power-value");if(y)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;y.innerHTML=`${d.format(i)}A`}else y.innerHTML=`${x(f)}${b(f)}`;const w=s.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(v?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=v?"On":"Off")}s.classList.toggle("circuit-off",!v),s.classList.toggle("circuit-producer",g);const C=c.entities?.select,k=C?e.states[C]:null,S=k?k.state:"unknown",$=l[S]||l.unknown,E=s.querySelector(".shedding-icon");E&&(E.setAttribute("icon",$.icon),E.style.color=$.color,E.title=$.label);const M=s.querySelector(".chart-container");if(M){j(M,e,a.get(o)||[],r,d,g,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("button");s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a&&!1!==a.monitoring_enabled;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),o=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:o,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?F(l):null,_=f?W(l):null,v=D(l,e,n,new Set([d,g,m,_].filter(Boolean))),y=I(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${b(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index d381149..43a43cd 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e,!0)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:i.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function _(t){return(Math.abs(t)/1e3).toFixed(1)}function y(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return y(e)===y(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,_=f&&parseFloat(f.state)||0,y=e.device_type===i||_<0,x=e.entities?.switch,w=x?c.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(_)}${b(_)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function P(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function R(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=_(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=_(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=_(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=_(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,_=f?e.states[f]:null,y=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=l[S]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=y(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,_=g?F(l):null,y=P(l,e,n,new Set([d,m,f,_].filter(Boolean))),x=R(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${y}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(t,n,i){let o;void 0!==i&&(this._configEntryId=i);try{const t={};this._configEntryId&&(t.config_entry_id=this._configEntryId);const i=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:t,return_response:!0});o=i?.response||null}catch{o=null}const a=o?.global_settings||{},s=!0===o?.enabled,r=o?.circuits||{},c=o?.mains||{},l=Object.entries(r).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),d=Object.entries(c),p=[...l,...d],h=p.length>0&&p.every(([,t])=>!1!==t.monitoring_enabled),g=p.some(([,t])=>!1!==t.monitoring_enabled),m=l.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),f=Object.entries(c).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${f}\n ${m}\n \n
NameContinuousSpikeWindowCooldown
\n \n
\n
\n `;const b=t.querySelector("#toggle-all-circuits");b&&!h&&g&&(b.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,r,c),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_serviceData(t){return this._configEntryId&&(t.config_entry_id=this._configEntryId),t}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:this._serviceData({...n})})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:t,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:t,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:o,monitoring_enabled:a})})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:o,monitoring_enabled:a})})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:this._serviceData({[l]:a,[s]:i})}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:o}:{circuit_id:o});await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e)&&!t.via_device_id);const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;await this._monitoringTab.render(t,this._hass,n);break}case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),o=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:o?Number(o.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function _(t){return(Math.abs(t)/1e3).toFixed(1)}function y(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return y(e)===y(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,_=f&&parseFloat(f.state)||0,y=e.device_type===i||_<0,x=e.entities?.switch,w=x?c.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(_)}${b(_)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=_(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=_(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=_(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=_(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,_=f?e.states[f]:null,y=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,S=$?e.states[$]:null,k=S?S.state:"unknown",C=l[k]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=y(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,_=g?F(l):null,y=R(l,e,n,new Set([d,m,f,_].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${y}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=S(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(t,n,i){let o;void 0!==i&&(this._configEntryId=i);try{const t={};this._configEntryId&&(t.config_entry_id=this._configEntryId);const i=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:t,return_response:!0});o=i?.response||null}catch{o=null}const a=o?.global_settings||{},s=!0===o?.enabled,r=o?.circuits||{},c=o?.mains||{},l=Object.entries(r).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),d=Object.entries(c),p=[...l,...d],h=p.length>0&&p.every(([,t])=>!1!==t.monitoring_enabled),g=p.some(([,t])=>!1!==t.monitoring_enabled),m=l.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),f=Object.entries(c).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${f}\n ${m}\n \n
NameContinuousSpikeWindowCooldown
\n \n
\n
\n `;const b=t.querySelector("#toggle-all-circuits");b&&!h&&g&&(b.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,r,c),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_serviceData(t){return this._configEntryId&&(t.config_entry_id=this._configEntryId),t}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:this._serviceData({...n})})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:t,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:t,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:o,monitoring_enabled:a})})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:o,monitoring_enabled:a})})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:this._serviceData({[l]:a,[s]:i})}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:o}:{circuit_id:o});await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e)&&!t.via_device_id);const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;await this._monitoringTab.render(t,this._hass,n);break}case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index fbad81f..67fcd0f 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -97,7 +97,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption) { if (gridStateEl) { const gridEid = topology.panel_entities?.dsm_state; const gridState = gridEid ? hass.states[gridEid] : null; - gridStateEl.textContent = gridState ? gridState.state : "--"; + gridStateEl.textContent = gridState ? hass.formatEntityState?.(gridState) || gridState.state : "--"; } } diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 4a1a503..9928f88 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -446,7 +446,7 @@ class SpanSidePanel extends HTMLElement { thresholdsWrap.appendChild(this._createThresholdRow("Continuous %", "continuous", continuousVal, cfg)); thresholdsWrap.appendChild(this._createThresholdRow("Spike %", "spike", spikeVal, cfg)); thresholdsWrap.appendChild(this._createDurationRow("Window duration", "window-m", windowVal, 1, 180, "m", cfg)); - thresholdsWrap.appendChild(this._createDurationRow("Cooldown", "cooldown-m", cooldownVal, 1, 180, "m", cfg, true)); + thresholdsWrap.appendChild(this._createDurationRow("Cooldown", "cooldown-m", cooldownVal, 1, 180, "m", cfg)); detailsWrap.appendChild(thresholdsWrap); // Event: monitoring enable toggle @@ -498,11 +498,14 @@ class SpanSidePanel extends HTMLElement { const continuous = this.shadowRoot.querySelector('[data-role="threshold-continuous"]'); const spike = this.shadowRoot.querySelector('[data-role="threshold-spike"]'); const windowM = this.shadowRoot.querySelector('[data-role="threshold-window-m"]'); + const cooldownM = this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'); + const entityId = cfg.entities?.power || cfg.uuid; this._callDomainService("set_circuit_threshold", { - circuit_id: cfg.uuid, + circuit_id: entityId, continuous_threshold_pct: continuous ? Number(continuous.value) : undefined, spike_threshold_pct: spike ? Number(spike.value) : undefined, window_duration_m: windowM ? Number(windowM.value) : undefined, + cooldown_duration_m: cooldownM ? Number(cooldownM.value) : undefined, }).catch(err => this._showError(`Save threshold failed: ${err.message ?? err}`)); }); }); From 985b9e1a8482238e7353ea9d77ed2c5ac17ec6c5 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:19:01 -0700 Subject: [PATCH 031/101] feat: add notification settings to monitoring tab Add notification configuration UI to global settings section: - Multi-select dropdown for notify targets, discovered from person entity device_trackers, notify entities, and legacy services - Priority dropdown (default/passive/active/time-sensitive/critical) with contextual hint text per level - Persistent notifications and event bus toggle checkboxes - Customizable title and message templates with placeholder reference --- dist/span-panel.js | 2 +- src/panel/tab-monitoring.js | 254 ++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 1 deletion(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index 43a43cd..9ef9f7a 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(l).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e)),c.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),o=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:o?Number(o.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function v(t){return(t<0?"-":"")+f.format(t)}function _(t){return(Math.abs(t)/1e3).toFixed(1)}function y(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return y(e)===y(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function S(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class k{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,c,p,h,g){const m=e.entities?.power,f=m?c.states[m]:null,_=f&&parseFloat(f.state)||0,y=e.device_type===i||_<0,x=e.entities?.switch,w=x?c.states[x]:null,S=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,k=e.breaker_rating_a,C=k?`${Math.round(k)}A`:"",E=u(e.name||"Unknown"),M=$(p);let T;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;T=`${M.format(i)}A`}else T=`${v(_)}${b(_)}`;const z=l[g||"unknown"]||l.unknown,L=``,N=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=N?d:"#555",I=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${T}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${S?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${A}\n ${I}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},z={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function N(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return N(t,M)}function I(t){return N(t,T)}function A(t){return N(t,z)}function F(t){return N(t,L)}function R(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function j(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function H(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,c,l){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",c=`rgb(${s})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:c}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=j(a);let c=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(c+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=_(o)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=_(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=_(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=_(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(t,e,o,a,c);const d=$(a),p="current"===d.entityRole;for(const[a,c]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=c.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=c.device_type===i||g<0,f=c.entities?.switch,_=f?e.states[f]:null,y=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${v(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=y?"On":"Off")}o.classList.toggle("circuit-off",!y),o.classList.toggle("circuit-producer",m);const $=c.entities?.select,S=$?e.states[$]:null,k=S?S.state:"unknown",C=l[k]||l.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const M=o.querySelector(".chart-container");if(M){G(M,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=j(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${v(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=o.get(n)||[];let s=c.power;n.endsWith("_soc")?s=c.soc:n.endsWith("_soe")&&(s=c.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=I(i),t.soe=A(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=j(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=S(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=H(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,c))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new k,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),c=(j(i),this._monitoringCache.status),l=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(c),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=y(Math.max(...n));0===x(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!c.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,c),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===o&&!i)continue;if(l.type===a&&!s)continue;const t=l.type===a?"EV Charger":l.type===o?"Battery":"Sub-device",d=q(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=l.type===o,m=g?I(l):null,f=g?A(l):null,_=g?F(l):null,y=R(l,e,n,new Set([d,m,f,_].filter(Boolean))),x=P(c,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${v(h)} ${b(h)}`:""}\n
\n ${x}\n ${y}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${l}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=j(this._config),e=W(t),n=H(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=S(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=this._powerHistory.get(t)||[];l.length>0&&i-l[l.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n";function Z(t,e,n,i,o){return`\n ${i}\n `}class tt{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(t,n,i){let o;void 0!==i&&(this._configEntryId=i);try{const t={};this._configEntryId&&(t.config_entry_id=this._configEntryId);const i=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:t,return_response:!0});o=i?.response||null}catch{o=null}const a=o?.global_settings||{},s=!0===o?.enabled,r=o?.circuits||{},c=o?.mains||{},l=Object.entries(r).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),d=Object.entries(c),p=[...l,...d],h=p.length>0&&p.every(([,t])=>!1!==t.monitoring_enabled),g=p.some(([,t])=>!1!==t.monitoring_enabled),m=l.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),f=Object.entries(c).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${Z(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${Z(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${Z(s,"window_duration_m",e.window_duration_m,"m","mains")}\n ${Z(s,"cooldown_duration_m",e.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${f}\n ${m}\n \n
NameContinuousSpikeWindowCooldown
\n \n
\n
\n `;const b=t.querySelector("#toggle-all-circuits");b&&!h&&g&&(b.indeterminate=!0),this._bindGlobalControls(t,n),this._bindToggleAll(t,n,r,c),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_serviceData(t){return this._configEntryId&&(t.config_entry_id=this._configEntryId),t}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:this._serviceData({...n})})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:t,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:t,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:o,monitoring_enabled:a})})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:o,monitoring_enabled:a})})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:c,service_data:this._serviceData({[l]:a,[s]:i})}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:o}:{circuit_id:o});await n.callService(e,s,r),await this.render(t,n)})}}class et{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class nt extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new tt,this._settingsTab=new et}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e)&&!t.via_device_id);const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;await this._monitoringTab.render(t,this._hass,n);break}case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",nt),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},l={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},c={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(c).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=c[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=r?"block":"none",n.appendChild(l);const c=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,l.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=c?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e)),l.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;l.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),o=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:o?Number(o.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const l=document.createElement("div");l.className="field-row";const c=document.createElement("span");c.className="field-label",c.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),l.appendChild(c),l.appendChild(d),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function y(t){return(t<0?"-":"")+f.format(t)}function v(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,l,p,h,g){const m=e.entities?.power,f=m?l.states[m]:null,v=f&&parseFloat(f.state)||0,_=e.device_type===i||v<0,x=e.entities?.switch,w=x?l.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),T=$(p);let z;if("current"===T.entityRole){const t=e.entities?.current,n=t?l.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${T.format(i)}A`}else z=`${y(v)}${b(v)}`;const M=c[g||"unknown"]||c.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=L?d:"#555",A=``;let I="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);I=`${Math.round(t)}%`}const j=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${I}\n ${A}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},M={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return L(t,T)}function A(t){return L(t,z)}function I(t){return L(t,M)}function j(t){return L(t,N)}function F(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=t.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(l)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function R(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function H(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function W(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,l,c){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",l=`rgb(${s})`,c=Date.now(),d=c-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:l}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(l||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=R(a);let l=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(l+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=v(o)),r&&(r.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),c=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",c&&(c.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=v(t),c&&(c.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=v(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=v(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(t,e,o,a,l);const d=$(a),p="current"===d.entityRole;for(const[a,l]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=l.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=l.device_type===i||g<0,f=l.entities?.switch,v=f?e.states[f]:null,_=v?"on"===v.state:(h?.attributes?.relay_state||l.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=l.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${y(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}o.classList.toggle("circuit-off",!_),o.classList.toggle("circuit-producer",m);const $=l.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=c[S]||c.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const T=o.querySelector(".chart-container");if(T){G(T,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=R(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${y(i)} ${b(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const t of c){const n=t.dataset.chartKey,i=o.get(n)||[];let s=l.power;n.endsWith("_soc")?s=l.soc:n.endsWith("_soe")&&(s=l.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=A(i),t.soe=I(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=R(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=H(i),l=W(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,l))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),l=(R(i),this._monitoringCache.status),c=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(l),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const l=new Set,c=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?l.add(i):c.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!l.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!c.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,l),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[l,c]of Object.entries(t.sub_devices)){if(c.type===o&&!i)continue;if(c.type===a&&!s)continue;const t=c.type===a?"EV Charger":c.type===o?"Battery":"Sub-device",d=q(c),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=c.type===o,m=g?A(c):null,f=g?I(c):null,v=g?j(c):null,_=F(c,e,n,new Set([d,m,f,v].filter(Boolean))),x=P(l,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(c.name||"")}\n ${d?`${y(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${c}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=R(this._config),e=H(t),n=W(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(t)||[];c.length>0&&i-c[c.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",Z="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",tt="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function et(t,e,n,i,o){return`\n ${i}\n `}class nt{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(t,n,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const t={};this._configEntryId&&(t.config_entry_id=this._configEntryId);const i=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:t,return_response:!0});o=i?.response||null}catch{o=null}const a=o?.global_settings||{},s=!0===o?.enabled,r=o?.circuits||{},l=o?.mains||{},c=new Set;for(const[t,e]of Object.entries(n.states||{})){if(!t.startsWith("person."))continue;const n=e.attributes?.device_trackers||[];for(const t of n){const e=t.split(".")[1];e&&c.add(`notify.mobile_app_${e}`)}}for(const t of Object.keys(n.states||{}))t.startsWith("notify.")&&c.add(t);for(const t of Object.keys(n.services?.notify||{}))c.add(`notify.${t}`);const d=[...c].sort(),p=a.notify_targets||"notify.notify",h=("string"==typeof p?p.split(","):p).map(t=>t.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,y=a.notification_priority||"default",v=Object.entries(r).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),_=Object.entries(l),x=[...v,..._],w=x.length>0&&x.every(([,t])=>!1!==t.monitoring_enabled),$=x.some(([,t])=>!1!==t.monitoring_enabled),k=v.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${et(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${et(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${et(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n ${et(s,"cooldown_duration_m",e.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),S=Object.entries(l).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${et(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${et(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${et(s,"window_duration_m",e.window_duration_m,"m","mains")}\n ${et(s,"cooldown_duration_m",e.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n\n
\n

Notification Settings

\n\n
\n Notify Targets\n
\n \n
\n ${0===d.length?'
No notify targets found
':d.map(t=>{const e=h.includes(t),i=n.states[t],o=i?.attributes?.friendly_name,a=o?`${u(o)} (${u(t)})`:u(t);return``}).join("")}\n
\n
\n
\n\n
\n Persistent Alerts\n \n
\n\n
\n Event Bus\n \n
\n\n
\n Priority\n \n \n ${"critical"===y?"Overrides silent/DND":"time-sensitive"===y?"Breaks through Focus":"passive"===y?"Delivers silently":"active"===y?"Standard delivery":""}\n \n
\n\n
\n Title Template\n \n
\n\n
\n Message Template\n \n
\n\n
\n Placeholders: {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${S}\n ${k}\n \n
NameContinuousSpikeWindowCooldown
\n \n
\n
\n `;const C=t.querySelector("#toggle-all-circuits");C&&!w&&$&&(C.indeterminate=!0),this._bindGlobalControls(t,n),this._bindNotifyTargetSelect(t,n),this._bindNotificationSettings(t,n),this._bindToggleAll(t,n,r,l),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_serviceData(t){return this._configEntryId&&(t.config_entry_id=this._configEntryId),t}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:this._serviceData({...n})})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindNotifyTargetSelect(t,e){const n=t.querySelector("#notify-target-btn"),i=t.querySelector("#notify-target-dropdown"),o=t.querySelector("#notify-target-label");if(!n||!i)return;n.addEventListener("click",t=>{t.stopPropagation();const e="none"!==i.style.display;i.style.display=e?"none":"block"});const a=e=>{const n=t.querySelector("#notify-target-select");n&&!n.contains(e.target)&&(i.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const n of t.querySelectorAll(".notify-target-cb"))n.addEventListener("change",()=>{const n=[...t.querySelectorAll(".notify-target-cb:checked")].map(t=>t.value);o.textContent=n.length?n.join(", "):"None selected",clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(e,{notify_targets:n.join(", ")})}catch{}},500)})}_bindNotificationSettings(t,e){const n=t.querySelector("#g-persistent-notifications"),i=t.querySelector("#g-event-bus"),o=t.querySelector("#g-priority"),a=t.querySelector("#g-title-template"),s=t.querySelector("#g-message-template"),r=(t,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(e,{[t]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(e,{notification_priority:o.value}),await this.render(t,e)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:t,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:t,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:o,monitoring_enabled:a})})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:o,monitoring_enabled:a})})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:l,service_data:this._serviceData({[c]:a,[s]:i})}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:o}:{circuit_id:o});await n.callService(e,s,r),await this.render(t,n)})}}class it{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class ot extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new nt,this._settingsTab=new it}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e)&&!t.via_device_id);const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;await this._monitoringTab.render(t,this._hass,n);break}case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",ot),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 67ebcd6..1005e1b 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -13,6 +13,16 @@ const INPUT_STYLE = ` const LABEL_STYLE = ` min-width:130px;font-size:0.85em;color:var(--secondary-text-color); `; +const WIDE_LABEL_STYLE = ` + min-width:160px;font-size:0.85em;color:var(--secondary-text-color); +`; +const TEXT_INPUT_STYLE = ` + background:var(--secondary-background-color,#333); + border:1px solid var(--divider-color); + color:var(--primary-text-color); + border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em; + font-family:monospace; +`; const CELL_INPUT_STYLE = ` background:var(--secondary-background-color,#333); border:1px solid var(--divider-color); @@ -37,6 +47,10 @@ export class MonitoringTab { async render(container, hass, configEntryId) { if (configEntryId !== undefined) this._configEntryId = configEntryId; + if (this._notifyCloseHandler) { + document.removeEventListener("click", this._notifyCloseHandler); + this._notifyCloseHandler = null; + } let status; try { const serviceData = {}; @@ -58,6 +72,42 @@ export class MonitoringTab { const circuits = status?.circuits || {}; const mains = status?.mains || {}; + // Discover notify targets from three sources, deduplicated: + // 1. Mobile app services derived from person entity device_trackers + // 2. Entity-based notify targets (notify.*) from hass.states + // 3. Legacy service-based targets from hass.services.notify + const targetSet = new Set(); + + // Derive mobile app notify services from person entities + device_trackers + for (const [eid, stateObj] of Object.entries(hass.states || {})) { + if (!eid.startsWith("person.")) continue; + const trackers = stateObj.attributes?.device_trackers || []; + for (const tracker of trackers) { + const deviceName = tracker.split(".")[1]; + if (deviceName) targetSet.add(`notify.mobile_app_${deviceName}`); + } + } + + // Add notify.* entities from hass.states + for (const eid of Object.keys(hass.states || {})) { + if (eid.startsWith("notify.")) targetSet.add(eid); + } + + // Add legacy service-based targets from hass.services.notify + for (const svc of Object.keys(hass.services?.notify || {})) { + targetSet.add(`notify.${svc}`); + } + + const allNotifyTargets = [...targetSet].sort(); + + const rawTargets = globalSettings.notify_targets || "notify.notify"; + const selectedTargets = (typeof rawTargets === "string" ? rawTargets.split(",") : rawTargets).map(t => t.trim()).filter(Boolean); + const titleTemplate = globalSettings.notification_title_template || "SPAN: {name} {alert_type}"; + const messageTemplate = globalSettings.notification_message_template || "{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)"; + const persistentNotifications = globalSettings.enable_persistent_notifications !== false; + const eventBus = globalSettings.enable_event_bus !== false; + const currentPriority = globalSettings.notification_priority || "default"; + const circuitEntries = Object.entries(circuits).sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || "")); const mainsEntries = Object.entries(mains); const allPoints = [...circuitEntries, ...mainsEntries]; @@ -175,6 +225,116 @@ export class MonitoringTab { value="${globalSettings.cooldown_duration_m ?? 15}" style="${INPUT_STYLE}">
+ +
+

Notification Settings

+ +
+ Notify Targets +
+ +
+ ${ + allNotifyTargets.length === 0 + ? `
No notify targets found
` + : allNotifyTargets + .map(target => { + const checked = selectedTargets.includes(target); + const stateObj = hass.states[target]; + const friendly = stateObj?.attributes?.friendly_name; + const displayName = friendly ? `${escapeHtml(friendly)} (${escapeHtml(target)})` : escapeHtml(target); + return ``; + }) + .join("") + } +
+
+
+ +
+ Persistent Alerts + +
+ +
+ Event Bus + +
+ +
+ Priority + + + ${ + currentPriority === "critical" + ? "Overrides silent/DND" + : currentPriority === "time-sensitive" + ? "Breaks through Focus" + : currentPriority === "passive" + ? "Delivers silently" + : currentPriority === "active" + ? "Standard delivery" + : "" + } + +
+ +
+ Title Template + +
+ +
+ Message Template + +
+ +
+ Placeholders: {name} {entity_id} {alert_type} + {current_a} {breaker_rating_a} {threshold_pct} + {utilization_pct} {window_m} +
@@ -217,6 +377,8 @@ export class MonitoringTab { } this._bindGlobalControls(container, hass); + this._bindNotifyTargetSelect(container, hass); + this._bindNotificationSettings(container, hass); this._bindToggleAll(container, hass, circuits, mains); this._bindCircuitToggles(container, hass); this._bindMainsToggles(container, hass); @@ -296,6 +458,98 @@ export class MonitoringTab { } } + _bindNotifyTargetSelect(container, hass) { + const btn = container.querySelector("#notify-target-btn"); + const dropdown = container.querySelector("#notify-target-dropdown"); + const label = container.querySelector("#notify-target-label"); + if (!btn || !dropdown) return; + + btn.addEventListener("click", e => { + e.stopPropagation(); + const isOpen = dropdown.style.display !== "none"; + dropdown.style.display = isOpen ? "none" : "block"; + }); + + // Close dropdown when clicking outside + const closeHandler = e => { + const selectEl = container.querySelector("#notify-target-select"); + if (selectEl && !selectEl.contains(e.target)) { + dropdown.style.display = "none"; + } + }; + document.addEventListener("click", closeHandler); + // Store ref for cleanup on next render (dropdown rebuilt each render) + this._notifyCloseHandler = closeHandler; + + // Handle checkbox changes + for (const cb of container.querySelectorAll(".notify-target-cb")) { + cb.addEventListener("change", () => { + const checked = [...container.querySelectorAll(".notify-target-cb:checked")]; + const targets = checked.map(c => c.value); + label.textContent = targets.length ? targets.join(", ") : "None selected"; + + clearTimeout(this._debounceTimer); + this._debounceTimer = setTimeout(async () => { + try { + await this._callSetGlobal(hass, { notify_targets: targets.join(", ") }); + } catch { + // will show on next render + } + }, 500); + }); + } + } + + _bindNotificationSettings(container, hass) { + const persistentCb = container.querySelector("#g-persistent-notifications"); + const eventBusCb = container.querySelector("#g-event-bus"); + const prioritySelect = container.querySelector("#g-priority"); + const titleInput = container.querySelector("#g-title-template"); + const messageInput = container.querySelector("#g-message-template"); + + const saveField = (field, value) => { + clearTimeout(this._debounceTimer); + this._debounceTimer = setTimeout(async () => { + try { + await this._callSetGlobal(hass, { [field]: value }); + } catch { + // will show on next render + } + }, 500); + }; + + if (persistentCb) { + persistentCb.addEventListener("change", () => { + saveField("enable_persistent_notifications", persistentCb.checked); + }); + } + if (eventBusCb) { + eventBusCb.addEventListener("change", () => { + saveField("enable_event_bus", eventBusCb.checked); + }); + } + if (prioritySelect) { + prioritySelect.addEventListener("change", async () => { + try { + await this._callSetGlobal(hass, { notification_priority: prioritySelect.value }); + await this.render(container, hass); + } catch { + // will show on next render + } + }); + } + if (titleInput) { + titleInput.addEventListener("input", () => { + saveField("notification_title_template", titleInput.value); + }); + } + if (messageInput) { + messageInput.addEventListener("input", () => { + saveField("notification_message_template", messageInput.value); + }); + } + } + _bindToggleAll(container, hass, circuits, mains) { const toggleAll = container.querySelector("#toggle-all-circuits"); if (!toggleAll) return; From dc6bcdb12d7290a732ed1193ba8ba1680f32da9d Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:36:45 -0700 Subject: [PATCH 032/101] feat: add i18n support with translations for en, es, fr, ja, pt Add internationalization system to the frontend card: - src/i18n.js with setLanguage()/t() API, auto-detects from hass.language - All ~110 hardcoded English strings replaced with t() calls across 13 files - Full translations for Spanish, French, Japanese, and Portuguese - Pre-commit validation script (scripts/validate-i18n.mjs) ensures all t() keys exist in every language and no orphaned keys accumulate - Lefthook hook added to run i18n validation on staged JS files --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- lefthook.yml | 3 + scripts/validate-i18n.mjs | 165 +++++++ src/card/card-discovery.js | 7 +- src/card/span-panel-card.js | 5 +- src/constants.js | 18 +- src/core/dom-updater.js | 5 +- src/core/grid-renderer.js | 9 +- src/core/header-renderer.js | 19 +- src/core/monitoring-status.js | 9 +- src/core/side-panel.js | 47 +- src/core/sub-device-renderer.js | 10 +- src/editor/span-panel-card-editor.js | 27 +- src/i18n.js | 683 +++++++++++++++++++++++++++ src/panel/span-panel.js | 8 +- src/panel/tab-monitoring.js | 75 +-- src/panel/tab-settings.js | 9 +- 18 files changed, 984 insertions(+), 119 deletions(-) create mode 100755 scripts/validate-i18n.mjs create mode 100644 src/i18n.js diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 4a82c0b..7d395ca 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",s="bess",o="evse",a="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},c={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},l={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}function h(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function g(t,e,n,i,s,o){t.has(e)||t.set(e,[]);const a=t.get(e);for(a.push({time:i,value:n});a.length>0&&a[0].timeo&&a.splice(0,a.length-o)}function m(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function _(e){return r[e.chart_metric]||r[t]}function v(t,e){const n=function(t){return _(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}const y=r.power;function b(t){return y.unit(t)}function x(t){return(t<0?"-":"")+y.format(t)}function w(t){return(Math.abs(t)/1e3).toFixed(1)}function C(t){return Math.ceil(t/2)}function k(t){return t%2==0?1:0}function S(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return C(e)===C(n)?"row-span":k(e)===k(n)?"col-span":"row-span"}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function E(t,e,s,o,a,r,c,p,h,f){const g=e.entities?.power,m=g?c.states[g]:null,v=m&&parseFloat(m.state)||0,y=e.device_type===i||v<0,w=e.entities?.switch,C=w?c.states[w]:null,k=C?"on"===C.state:(m?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,$=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),M=_(p);let z;if("current"===M.entityRole){const t=e.entities?.current,n=t?c.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${M.format(i)}A`}else z=`${x(v)}${b(v)}`;const T=l[f||"unknown"]||l.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),P=L?d:"#555",R=``;let A="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);A=`${Math.round(t)}%`}const F=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${A}\n ${R}\n
\n
\n
\n `}function M(t,e){return`\n
\n \n
\n `}const z={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},N={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function P(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function R(t){return P(t,z)}function A(t){return P(t,T)}function F(t){return P(t,N)}function W(t){return P(t,L)}function D(t,e,n,i){const s=n.visible_sub_entities||{};let o="";if(!t.entities)return o;for(const[n,a]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const r=e.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=t.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${u(c)}:\n ${u(d)}\n
\n `}return o}function I(t,e,n,i,s,o){if(n){return`\n
\n ${[{key:`${a}${t}_soc`,title:"SoC",available:!!s},{key:`${a}${t}_soe`,title:"SoE",available:!!o},{key:`${a}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}async function O(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await t.callWS({type:"history/history_during_period",start_time:o,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(t){return Math.max(500,Math.floor(t/5e3))}(i);for(const[t,e]of Object.entries(a)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];s.set(i,m(e,r,c))}}}function q(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:R(i)};i.type===s&&(t.soc=A(i),t.soe=F(i));for(const[i,s]of Object.entries(t))s&&e.push({entityId:s,key:`${a}${n}_${i}`})}return e}async function H(t,e,n,i){if(!e||!t)return;const s=h(n),o=[],a=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=v(i,n);e&&(o.push(e),a.set(e,t))}if(function(t,e,n){for(const{entityId:i,key:s}of q(t))e.push(i),n.set(i,s)}(e,o,a),0===o.length)return;s>72e5?await async function(t,e,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:e,period:a,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const o=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&o.push({time:n,value:e})}if(o.length>0){const t=s.get(i)||[],e=[...o,...t];e.sort((t,e)=>t.time-e.time),s.set(i,e)}}}(t,o,a,s,i):await O(t,o,a,s,i)}function j(e,n,i,s,o,a,c,l){const{options:d,series:p}=function(e,n,i,s,o){i||(i=r[t]);const a=s?"140, 160, 220":"77, 217, 175",c=`rgb(${a})`,l=Date.now(),d=l-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:c}}],f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(f.min=i.fixedMin,f.max=i.fixedMax),o&&"current"===i.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),h.push({type:"line",data:[[d,.8*o],[l,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,o],[l,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,s,o,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(c||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function U(t,e,s,o,a){if(!t||!s||!e)return;const r=h(o);let c=0;for(const[,t]of Object.entries(s.circuits)){const n=t.entities?.power;if(!n)continue;const s=e.states[n],o=s&&parseFloat(s.state)||0;t.device_type!==i&&(c+=Math.abs(o))}!function(t,e,n,i,s){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,s=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(s=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=w(s)),r&&(r.textContent="kW")}const c=t.querySelector(".stat-upstream .stat-value"),l=t.querySelector(".stat-upstream .stat-unit");if(c){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",l&&(l.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=w(t),l&&(l.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=w(t)}else u.textContent="--";h&&(h.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(t,e,s,o,c);const d=_(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(s.circuits)){const s=t.querySelector(`[data-uuid="${o}"]`);if(!s)continue;const u=c.entities?.power,h=u?e.states[u]:null,f=h&&parseFloat(h.state)||0,g=c.device_type===i||f<0,m=c.entities?.switch,_=m?e.states[m]:null,v=_?"on"===_.state:(h?.attributes?.relay_state||c.relay_state)===n,y=s.querySelector(".power-value");if(y)if(p){const t=c.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;y.innerHTML=`${d.format(i)}A`}else y.innerHTML=`${x(f)}${b(f)}`;const w=s.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(v?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=v?"On":"Off")}s.classList.toggle("circuit-off",!v),s.classList.toggle("circuit-producer",g);const C=c.entities?.select,k=C?e.states[C]:null,S=k?k.state:"unknown",$=l[S]||l.unknown,E=s.querySelector(".shedding-icon");E&&(E.setAttribute("icon",$.icon),E.style.color=$.color,E.title=$.label);const M=s.querySelector(".chart-container");if(M){j(M,e,a.get(o)||[],r,d,g,s.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function G(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}const B=Object.keys(l).filter(t=>"unknown"!==t);class V extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const s=document.createElement("div");s.className="panel",e.appendChild(s),t.panelMode?this._renderPanelMode(s):this._renderCircuitMode(s,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const s=document.createElement("button");s.textContent="Configure Global Thresholds",Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const s=document.createElement("div");s.className="panel-body",t.appendChild(s);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",s.appendChild(o),this._renderRelaySection(s,e),this._renderSheddingSection(s,e),this._renderMonitoringSection(s,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const s=document.createElement("button");return s.className="close-btn",s.innerHTML="✕",s.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(s),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Breaker";const o=document.createElement("ha-switch");o.dataset.role="relay-toggle";const a=e.entities.switch,r=this._hass?.states?.[a]?.state;"on"===r&&o.setAttribute("checked",""),o.addEventListener("change",()=>{const t=o.hasAttribute("checked")||o.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:a}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent="Priority";const o=document.createElement("select");o.dataset.role="shedding-select";const a=e.entities.select,r=this._hass?.states?.[a]?.state||"";for(const t of B){const e=document.createElement("option");e.value=t,e.textContent=l[t].label,t===r&&(e.selected=!0),o.appendChild(e)}o.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:o.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(s),i.appendChild(o),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent="Monitoring",s.style.margin="0";const o=document.createElement("ha-switch");o.dataset.role="monitoring-toggle";const a=e.monitoringInfo,r=null!=a&&!1!==a.monitoring_enabled;r&&o.setAttribute("checked",""),i.appendChild(s),i.appendChild(o),n.appendChild(i);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=r?"block":"none",n.appendChild(c);const l=void 0!==a?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=l?"block":"none";const u=a?.continuous_threshold_pct??80,h=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,g=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",f,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",g,1,180,"m",e)),c.appendChild(p),o.addEventListener("change",()=>{const t=o.checked;c.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=t;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${e}`,a.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),o=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:o,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),s.appendChild(o),s.appendChild(a),s}_createDurationRow(t,e,n,i,s,o,a,r=!1){const c=document.createElement("div");c.className="field-row";const l=document.createElement("span");l.className="field-label",l.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(s),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=o,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),c.appendChild(l),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}customElements.define("span-side-panel",V);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new $}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(t){this._config=t,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return h(this._config)}set hass(t){if(this._hass=t,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(t).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML='\n \n
\n Open the card editor and select your SPAN Panel device.\n
\n
\n '}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:t,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const t=await async function(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),s=i.panel_size||G(i.circuits);if(!s)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:s}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(t,n){const[i,s]=await Promise.all([t.callWS({type:"config/device_registry/list"}),t.callWS({type:"config/entity_registry/list"})]),o=i.find(t=>t.id===n)||null;if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(t=>t.device_id===n),r=i.filter(t=>t.via_device_id===n),c=new Set(r.map(t=>t.id)),l=s.filter(t=>c.has(t.device_id)),d={},p=o.name_by_user||o.name||"";for(const e of[...a,...l]){const n=t.states[e.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const s=i.slice(6,-1);let o;if(o=s.includes(":")?s.split(":").map(Number):[Number(s)],!o.every(Number.isFinite))continue;const a=e.unique_id.split("_");let r=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(a[t])){r=a[t];break}if(!r)continue;let c=n.attributes.friendly_name||e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");d[r]={tabs:o,name:c,voltage:n.attributes.voltage||(2===o.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:e.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(o.identifiers)for(const t of o.identifiers)t[0]===e&&(u=t[1]);let h=0;for(const e of a){const n=t.states[e.entity_id];if(n&&n.attributes&&n.attributes.panel_size){h=n.attributes.panel_size;break}}if(h||(h=G(d)),!h)throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.");const f={};for(const e of r){const n=s.filter(t=>t.device_id===e.id),i=(e.model||"").toLowerCase().includes("battery")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("bess")),o=(e.model||"").toLowerCase().includes("drive")||(e.identifiers||[]).some(t=>(t[1]||"").toLowerCase().includes("evse")),a={};for(const e of n)a[e.entity_id]={domain:e.entity_id.split(".")[0],original_name:t.states[e.entity_id]?.attributes?.friendly_name||e.entity_id};f[e.id]={name:e.name_by_user||e.name||"",type:i?"bess":o?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:o.sw_version||"",panel_size:h,device_id:n,device_name:o.name_by_user||o.name||"SPAN Panel",circuits:d,sub_devices:f},panelDevice:o,panelSize:h}}(this._hass,this._config.device_id);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._discoveryError=t.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await H(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const t=Date.now(),e=t-this._durationMs,n=f(this._durationMs);for(const[i,s]of Object.entries(this._topology.circuits)){const o=v(s,this._config);if(!o)continue;const a=this._hass.states[o],r=a&&parseFloat(a.state)||0;g(this._powerHistory,i,r,t,e,n)}for(const{entityId:i,key:s}of q(this._topology)){const o=this._hass.states[i],a=o&&parseFloat(o.state)||0;g(this._powerHistory,s,a,t,e,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){U(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(t,e,n,i,s){if(!n.sub_devices)return;const o=h(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=R(a);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${x(i)} ${b(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const t of l){const n=t.dataset.chartKey,i=s.get(n)||[];let a=c.power;n.endsWith("_soc")?a=c.soc:n.endsWith("_soe")&&(a=c.soe);const r=!!t.closest(".bess-chart-col");j(t,e,i,o,a,!1,r?120:150)}for(const t of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const s=e.states[t];s&&(i.textContent=`${s.state}${s.attributes.unit_of_measurement?" "+s.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(t){const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(t){const e=t.target.closest(".toggle-pill");if(!e)return;t.stopPropagation(),t.preventDefault();const n=e.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,s=this._topology.circuits[i];if(!s)return;const o=s.entities?.switch;if(!o)return;const a=this._hass.states[o];if(!a)return void console.warn("SPAN Panel: switch entity not found:",o);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:o}).catch(t=>{console.error("SPAN Panel: switch service call failed:",t)})}_onGearClick(t){const e=t.target.closest(".gear-icon");if(!e)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,e.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=e.dataset.uuid;if(!i||!this._topology)return;const s=this._topology.circuits[i];if(!s)return;const o=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;n.open({...s,uuid:i,monitoringInfo:o})}_render(){const t=this._hass;if(!t||!this._topology||!this._panelSize){const t=this._discoveryError||(this._topology?"Loading...":"Panel device not found. Check device_id in card config.");return void(this.shadowRoot.innerHTML=`\n \n
\n ${u(t)}\n
\n
\n `)}const e=this._topology,n=Math.ceil(this._panelSize/2),i=(this._durationMs,function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),s=u(t.firmware||""),o="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${o?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(e,this._config)),a=this._monitoringCache.status,r=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],s=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,o=i.filter(t=>t.utilization_pct>=100).length,a=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${s>0?`${s} warning${s>1?"s":""}`:""}\n ${o>0?`${o} alert${o>1?"s":""}`:""}\n ${a>0?`${a} override${a>1?"s":""}`:""}\n \n
\n `}(a),c=function(t,e,n,i,s,o){const a=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),s=1===t.length?"single":S(t);a.set(i,{uuid:e,circuit:n,layout:s});for(const e of t)r.add(e)}const c=new Set,l=new Set;for(const[t,e]of a)if("col-span"===e.layout){const n=e.circuit.tabs,i=C(Math.max(...n));0===k(t)?c.add(i):l.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=o?(s=o,a=e,s?.circuits&&s.circuits[a]||null):null;var s,a;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,o=a.get(e),u=a.get(n);if(p+=`
${e}
`,o&&"row-span"===o.layout){const{monInfo:e,sheddingPriority:a}=d(o);p+=E(o.uuid,o.circuit,t,"2 / 4","row-span",0,i,s,e,a),p+=`
${n}
`;continue}if(!c.has(t))if(!o||"col-span"!==o.layout&&"single"!==o.layout)r.has(e)||(p+=M(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(o);p+=E(o.uuid,o.circuit,t,"2",o.layout,0,i,s,e,n)}if(!l.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=M(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=E(u.uuid,u.circuit,t,"3",u.layout,0,i,s,e,n)}p+=`
${n}
`}return p}(e,n,0,t,this._config,a),l=function(t,e,n){const i=!1!==n.show_battery,a=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[c,l]of Object.entries(t.sub_devices)){if(l.type===s&&!i)continue;if(l.type===o&&!a)continue;const t=l.type===o?"EV Charger":l.type===s?"Battery":"Sub-device",d=R(l),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,f=l.type===s,g=f?A(l):null,m=f?F(l):null,_=f?W(l):null,v=D(l,e,n,new Set([d,g,m,_].filter(Boolean))),y=I(c,0,f,d,g,m);r+=`\n
\n
\n ${u(t)}\n ${u(l.name||"")}\n ${d?`${x(h)} ${b(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return r}(e,t,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${i}\n ${r}\n ${!1!==this._config.show_panel?`\n
\n ${c}\n
\n `:""}\n ${l?`
${l}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const d=this.shadowRoot.querySelector("span-side-panel");d&&(d.hass=t),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class X extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers||[]).some(t=>t[0]===e)&&!t.via_device_id).map(t=>{const n=(t.identifiers||[]).find(t=>t[0]===e)?.[1]||"",i=t.name_by_user||t.name||"SPAN Panel";return{device_id:t.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="SPAN Panel",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e;const r=document.createElement("option");if(r.value="",r.textContent="Select a panel...",a.appendChild(r),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._panelSelect=a}_buildTimeWindow(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart time window",o.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const r=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=r;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(a),{wrap:s,input:o}},l=parseInt(this._config.history_days)||0,d=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=c(l,"0","30","days"),h=c(d,"0","23","hours"),f=c(p,"0","59","minutes"),g=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(h.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",g),h.input.addEventListener("change",g),f.input.addEventListener("change",g),a.appendChild(u.wrap),a.appendChild(h.wrap),a.appendChild(f.wrap),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._daysInput=u.input,this._hoursInput=h.input,this._minsInput=f.input}_buildMetricSelector(t,e,n,i){const s=document.createElement("div");s.style.cssText=i;const o=document.createElement("label");o.textContent="Chart metric",o.style.cssText=n;const a=document.createElement("select");a.style.cssText=e,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),s.appendChild(o),s.appendChild(a),t.appendChild(s),this._metricSelect=a}_buildSectionCheckboxes(t,e,n){const i=document.createElement("div");i.style.cssText=n;const s=document.createElement("label");s.textContent="Visible sections",s.style.cssText=e,i.appendChild(s);const o=[{key:"show_panel",label:"Panel circuits",subDeviceType:null},{key:"show_battery",label:"Battery (BESS)",subDeviceType:"bess"},{key:"show_evse",label:"EV Charger (EVSE)",subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of o){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=t.label,s.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(s),i.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",i.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(i)}_isChartEntity(t,e,n){const i=(e.original_name||"").toLowerCase(),s=e.unique_id||"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(t)){const t=this._entityContainers[n.type];if(t&&(t.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=s.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(r),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities||{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const n=await this._hass.callWS({type:`${e}/panel_topology`,device_id:t}),i=new Set;for(const t of Object.values(n.circuits||{}))for(const e of Object.keys(t.entities||{}))i.add(e);this._availableRoles=i,this._populateMetricSelect(),n.sub_devices&&this._populateEntityCheckboxes(n.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const n=this._config.chart_metric||t;e.innerHTML="";for(const[t,i]of Object.entries(r)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=t,s.textContent=i.label,t===n&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||t),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",K),customElements.define("span-panel-card-editor",X),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="span_panel",o="CLOSED",a="pv",s="bess",r="evse",c="sub_",l={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},d={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:l.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},p="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function g(e){return String(e).replace(/[&<>"']/g,e=>h[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function _(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function v(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function b(e){return l[e.chart_metric]||l[n]}function y(e,t){const n=function(e){return b(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}const w=l.power;function x(e){return w.unit(e)}function C(e){return(e<0?"-":"")+w.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function E(e){return e%2==0?1:0}function $(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":E(t)===E(n)?"col-span":"row-span"}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:i,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function N(e,n,i,s,r,c,l,d,h,m){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,y=n.device_type===a||v<0,w=n.entities?.switch,S=w?l.states[w]:null,k=S?"on"===S.state:(_?.attributes?.relay_state||n.relay_state)===o,E=n.breaker_rating_a,$=E?`${Math.round(E)}A`:"",P=g(n.name||t("grid.unknown")),N=b(d);let A;if("current"===N.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;A=`${N.format(i)}A`}else A=`${C(v)}${x(v)}`;const M=u[m||"unknown"]||u.unknown,z=``,T=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),L=T?p:"#555",R=``;let D="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);D=`${Math.round(e)}%`}const F=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${P}\n
\n
\n \n ${A}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(k?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${z}\n ${D}\n ${R}\n
\n
\n
\n `}function A(e,t){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function R(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return R(e,M)}function F(e){return R(e,z)}function j(e){return R(e,T)}function I(e){return R(e,L)}function H(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${g(c)}:\n ${g(d)}\n
\n `}return a}function O(e,n,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:t("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:t("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${g(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function W(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,v(t,r,c))}}}function q(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===s&&(e.soc=F(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}async function V(e,t,n,i){if(!t||!e)return;const o=m(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=y(i,n);t&&(a.push(t),s.set(t,e))}if(function(e,t,n){for(const{entityId:i,key:o}of q(e))t.push(i),n.set(i,o)}(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await W(e,a,s,o,i)}function B(e,t,i,o,a,s,r,c){const{options:d,series:u}=function(e,t,i,o,a){i||(i=l[n]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,c=Date.now(),d=c-t,u=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return u&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:h}}(i,o,a,s,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=d,p.data=u}function G(e,n,i,s,r){if(!e||!i||!n)return;const c=m(s);let l=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==a&&(l+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=S(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),u=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),u&&(u.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=S(e)}else p.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,s,l);const d=b(s),p="current"===d.entityRole;for(const[s,l]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${s}"]`);if(!i)continue;const h=l.entities?.power,g=h?n.states[h]:null,m=g&&parseFloat(g.state)||0,f=l.device_type===a||m<0,_=l.entities?.switch,v=_?n.states[_]:null,b=v?"on"===v.state:(g?.attributes?.relay_state||l.relay_state)===o,y=i.querySelector(".power-value");if(y)if(p){const e=l.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;y.innerHTML=`${d.format(i)}A`}else y.innerHTML=`${C(m)}${x(m)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(b?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=t(b?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!b),i.classList.toggle("circuit-producer",f);const S=l.entities?.select,k=S?n.states[S]:null,E=k?k.state:"unknown",$=u[E]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",$.icon),P.style.color=$.color,P.title=$.label());const N=i.querySelector(".chart-container");if(N){B(N,n,r.get(s)||[],c,d,f,i.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const J=Object.keys(u).filter(e=>"unknown"!==e);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=t("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${g(String(t.breaker_rating_a))}A · ${g(String(t.voltage))}V · Tabs [${g(String(t.tabs))}]`,i=this._createHeader(g(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of J){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,u=document.createElement("div");u.className="radio-group",u.innerHTML=`\n \n \n `,l.appendChild(u);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",m,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",n)),l.appendChild(p),s.addEventListener("change",()=>{const e=s.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=u.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,n,i,o,a,s,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const u=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(a),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const h=document.createElement("span");return h.textContent=s,u.appendChild(p),u.appendChild(h),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(u),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(i,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",K);class X extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new P}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const o=await e.callWS({type:`${i}/panel_topology`,device_id:n}),a=o.panel_size||U(o.circuits);if(!a)throw new Error(t("card.topology_error"));return{topology:o,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:a}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[o,a]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=o.find(e=>e.id===n)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=a.filter(e=>e.device_id===n),c=o.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=a.filter(e=>l.has(e.device_id)),u={},p=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!a.every(Number.isFinite))continue;const s=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(s[e])){r=s[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");u[r]={tabs:a,name:c,voltage:n.attributes.voltage||(2===a.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(s.identifiers)for(const e of s.identifiers)e[0]===i&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=U(u)),!g)throw new Error(t("card.panel_size_error"));const m={};for(const t of c){const n=a.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),o=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),s={};for(const t of n)s[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};m[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":o?"evse":"unknown",entities:s}}return{topology:{serial:h,firmware:s.sw_version||"",panel_size:g,device_id:n,device_name:s.name_by_user||s.name||t("header.default_name"),circuits:u,sub_devices:m},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await V(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now(),t=e-this._durationMs,n=f(this._durationMs);for(const[i,o]of Object.entries(this._topology.circuits)){const a=y(o,this._config);if(!a)continue;const s=this._hass.states[a],r=s&&parseFloat(s.state)||0;_(this._powerHistory,i,r,e,t,n)}for(const{entityId:i,key:o}of q(this._topology)){const a=this._hass.states[i],s=a&&parseFloat(a.state)||0;_(this._powerHistory,o,s,e,t,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){G(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(e,t,n,i,o){if(!n.sub_devices)return;const a=m(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${C(i)} ${x(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let s=d.power;n.endsWith("_soc")?s=d.soc:n.endsWith("_soe")&&(s=d.soe);const r=!!e.closest(".bess-chart-col");B(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const a=o.entities?.switch;if(!a)return;const s=this._hass.states[a];if(!s)return void console.warn("SPAN Panel: switch entity not found:",a);const r="on"===s.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}_onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=t.dataset.uuid;if(!i||!this._topology)return;const o=this._topology.circuits[i];if(!o)return;const a=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;n.open({...o,uuid:i,monitoringInfo:a})}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${g(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=g(e.device_name||t("header.default_name")),o=g(e.serial||""),a=g(e.firmware||""),s="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,u=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(n,this._config)),a=this._monitoringCache.status,c=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${a>0?`${a} ${t(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${t(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(a),l=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":$(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===E(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let u="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),p=s.get(n);if(u+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);u+=N(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),u+=`
${n}
`;continue}if(!c.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(u+=A(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);u+=N(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(u+=A(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);u+=N(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}u+=`
${n}
`}return u}(n,i,0,e,this._config,a),d=function(e,n,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let c="";if(!e.sub_devices)return c;for(const[l,d]of Object.entries(e.sub_devices)){if(d.type===s&&!o)continue;if(d.type===r&&!a)continue;const e=d.type===r?t("subdevice.ev_charger"):d.type===s?t("subdevice.battery"):t("subdevice.fallback"),u=D(d),p=u?n.states[u]:null,h=p&&parseFloat(p.state)||0,m=d.type===s,f=m?F(d):null,_=m?j(d):null,v=m?I(d):null,b=H(d,n,i,new Set([u,f,_,v].filter(Boolean))),y=O(l,0,m,u,f,_);c+=`\n
\n
\n ${g(e)}\n ${g(d.name||"")}\n ${u?`${C(h)} ${x(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return c}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${c}\n ${!1!==this._config.show_panel?`\n
\n ${l}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class Q extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===i)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===i)?.[1]||"",o=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${o} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.panel_label"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_window"),s.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const a=document.createElement("input");a.type="number",a.min=t,a.max=n,a.value=String(e),a.style.cssText=c;const s=document.createElement("span");return s.textContent=i,s.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(a),o.appendChild(s),{wrap:o,input:a}},d=parseInt(this._config.history_days)||0,u=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(u,"0","23",t("editor.hours")),m=l(p,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(m.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),m.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(m.wrap),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=m.input}_buildMetricSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_metric"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const a=document.createElement("label");a.textContent=t("editor.visible_sections"),a.style.cssText=n,o.appendChild(a);const s=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of s){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let a=null;e.subDeviceType&&(a=document.createElement("div"),a.style.cssText="padding-left: 26px;",a.style.display=n.checked?"block":"none",o.appendChild(a),this._entityContainers[e.subDeviceType]=a),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},a&&(a.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const a=document.createElement("div");a.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const s=document.createElement("input");s.type="checkbox",s.checked=!0===t[i],s.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",a.appendChild(s),a.appendChild(r),e.appendChild(a),s.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};s.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${i}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(l)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",X),customElements.define("span-panel-card-editor",Q),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 9ef9f7a..a8fc8fe 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";const t="power",e="span_panel",n="CLOSED",i="pv",o="bess",a="evse",s="sub_",r={power:{entityRole:"power",label:"Power",unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>Math.abs(t)>=1e3?(Math.abs(t)/1e3).toFixed(1):String(Math.round(Math.abs(t)))},current:{entityRole:"current",label:"Current",unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},l={soc:{label:"State of Charge",unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{label:"State of Energy",unit:()=>"kWh",format:t=>t.toFixed(1)},power:r.power},c={never:{icon:"mdi:shield-check",color:"#4caf50",label:"Never"},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:"SoC Threshold"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:"Off-Grid"},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:"Unknown"}},d="#ff9800",p={"&":"&","<":"<",">":">",'"':""","'":"'"};function u(t){return String(t).replace(/[&<>"']/g,t=>p[t])}const h=Object.keys(c).filter(t=>"unknown"!==t);class g extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const o=document.createElement("div");o.className="panel",e.appendChild(o),t.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,t)}_renderPanelMode(t){const e=this._createHeader("Panel Monitoring","Global defaults for all circuits");t.appendChild(e);const n=document.createElement("div");n.className="panel-body";const i=document.createElement("div");i.className="panel-mode-info",i.innerHTML="\n

Global monitoring thresholds apply to all circuits that don't have custom overrides.\n Use the integration's options flow to change global settings.

\n

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row\n and switching to Custom mode.

\n ",n.appendChild(i);const o=document.createElement("button");o.textContent="Configure Global Thresholds",Object.assign(o.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),o.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(o),t.appendChild(n)}_renderCircuitMode(t,e){const n=`${u(String(e.breaker_rating_a))}A · ${u(String(e.voltage))}V · Tabs [${u(String(e.tabs))}]`,i=this._createHeader(u(e.name),n);t.appendChild(i);const o=document.createElement("div");o.className="panel-body",t.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,e),this._renderSheddingSection(o,e),this._renderMonitoringSection(o,e)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${t}
`+(e?`
${e}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Breaker";const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,r=this._hass?.states?.[s]?.state;"on"===r&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>this._showError(`Relay toggle failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML='';const i=document.createElement("div");i.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent="Priority";const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,r=this._hass?.states?.[s]?.state||"";for(const t of h){const e=document.createElement("option");e.value=t,e.textContent=c[t].label,t===r&&(e.selected=!0),a.appendChild(e)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>this._showError(`Shedding update failed: ${t.message??t}`))}),i.appendChild(o),i.appendChild(a),n.appendChild(i),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const i=document.createElement("div");i.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent="Monitoring",o.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,r=null!=s&&!1!==s.monitoring_enabled;r&&a.setAttribute("checked",""),i.appendChild(o),i.appendChild(a),n.appendChild(i);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=r?"block":"none",n.appendChild(l);const c=void 0!==s?.continuous_threshold_pct,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,l.appendChild(d);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=c?"block":"none";const u=s?.continuous_threshold_pct??80,h=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,m=s?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow("Continuous %","continuous",u,e)),p.appendChild(this._createThresholdRow("Spike %","spike",h,e)),p.appendChild(this._createDurationRow("Window duration","window-m",g,1,180,"m",e)),p.appendChild(this._createDurationRow("Cooldown","cooldown-m",m,1,180,"m",e)),l.appendChild(p),a.addEventListener("change",()=>{const t=a.checked;l.style.display=t?"block":"none";const n=e.entities?.power||e.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:t}).catch(t=>this._showError(`Monitoring toggle failed: ${t.message??t}`))});const f=d.querySelectorAll('input[type="radio"]');for(const t of f)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(p.style.display=n?"block":"none",!n&&t.checked){const t=e.entities?.power||e.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:t}).catch(t=>this._showError(`Clear monitoring failed: ${t.message??t}`))}});t.appendChild(n)}_createThresholdRow(t,e,n,i){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),o=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:o?Number(o.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,i,o,a,s,r=!1){const l=document.createElement("div");l.className="field-row";const c=document.createElement("span");c.className="field-label",c.textContent=t;const d=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(i),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${e}`,r&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,d.appendChild(p),d.appendChild(u),r||p.addEventListener("input",()=>{this._debounce(`threshold-${e}`,500,()=>{const t=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),e=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:e?Number(e.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(t=>this._showError(`Save threshold failed: ${t.message??t}`))})}),l.appendChild(c),l.appendChild(d),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(t.entities?.switch){const e=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,n){return this._callService(e,t,n)}_showError(t){const e=this.shadowRoot.getElementById("error-msg");e&&(e.textContent=t,e.style.display="block",setTimeout(()=>{e.style.display="none"},5e3))}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}async function m(t,n){const i=await t.callWS({type:`${e}/panel_topology`,device_id:n}),o=i.panel_size||function(t){let e=0;for(const n of Object.values(t||{}))for(const t of n.tabs||[])t>e&&(e=t);return e>0?e+e%2:0}(i.circuits);if(!o)throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.");return{topology:i,panelDevice:(await t.callWS({type:"config/device_registry/list"})).find(t=>t.id===n)||null,panelSize:o}}customElements.define("span-side-panel",g);const f=r.power;function b(t){return f.unit(t)}function y(t){return(t<0?"-":"")+f.format(t)}function v(t){return(Math.abs(t)/1e3).toFixed(1)}function _(t){return Math.ceil(t/2)}function x(t){return t%2==0?1:0}function w(t){if(2!==t.length)return null;const[e,n]=[Math.min(...t),Math.max(...t)];return _(e)===_(n)?"row-span":x(e)===x(n)?"col-span":"row-span"}function $(e){return r[e.chart_metric]||r[t]}function k(t,e){const n=function(t){return $(t).entityRole}(e);return t.entities?.[n]||t.entities?.power||null}class S{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await t.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function C(t,e,o,a,s,r,l,p,h,g){const m=e.entities?.power,f=m?l.states[m]:null,v=f&&parseFloat(f.state)||0,_=e.device_type===i||v<0,x=e.entities?.switch,w=x?l.states[x]:null,k=w?"on"===w.state:(f?.attributes?.relay_state||e.relay_state)===n,S=e.breaker_rating_a,C=S?`${Math.round(S)}A`:"",E=u(e.name||"Unknown"),T=$(p);let z;if("current"===T.entityRole){const t=e.entities?.current,n=t?l.states[t]:null,i=n&&parseFloat(n.state)||0;z=`${T.format(i)}A`}else z=`${y(v)}${b(v)}`;const M=c[g||"unknown"]||c.unknown,N=``,L=h&&function(t){return!!t&&void 0!==t.continuous_threshold_pct}(h),q=L?d:"#555",A=``;let I="";if(null!=h?.utilization_pct){const t=h.utilization_pct,e=function(t){if(!t?.utilization_pct)return"";const e=t.utilization_pct;return e>=100?"utilization-alert":e>=80?"utilization-warning":"utilization-normal"}(h);I=`${Math.round(t)}%`}const j=function(t){return!!t&&null!=t.over_threshold_since}(h);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${E}\n
\n
\n \n ${z}\n \n ${!1!==e.is_user_controllable&&e.entities?.switch?`\n
\n ${k?"On":"Off"}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${I}\n ${A}\n
\n
\n
\n `}function E(t,e){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},M={names:["state of energy"],suffixes:["_soe_kwh"]},N={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function L(t,e){if(!t.entities)return null;for(const[n,i]of Object.entries(t.entities)){if("sensor"!==i.domain)continue;const t=(i.original_name||"").toLowerCase();if(e.names.some(e=>t===e))return n;if(i.unique_id&&e.suffixes.some(t=>i.unique_id.endsWith(t)))return n}return null}function q(t){return L(t,T)}function A(t){return L(t,z)}function I(t){return L(t,M)}function j(t){return L(t,N)}function F(t,e,n,i){const o=n.visible_sub_entities||{};let a="";if(!t.entities)return a;for(const[n,s]of Object.entries(t.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=e.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=t.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),e.formatEntityState)d=e.formatEntityState(r);else{d=r.state;const t=r.attributes.unit_of_measurement||"";t&&(d+=" "+t)}if("Wh"===(r.attributes.unit_of_measurement||"")){const t=parseFloat(r.state);isNaN(t)||(d=(t/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${u(l)}:\n ${u(d)}\n
\n `}return a}function P(t,e,n,i,o,a){if(n){return`\n
\n ${[{key:`${s}${t}_soc`,title:"SoC",available:!!o},{key:`${s}${t}_soe`,title:"SoE",available:!!a},{key:`${s}${t}_power`,title:"Power",available:!!i}].filter(t=>t.available).map(t=>`\n
\n
${u(t.title)}
\n
\n
\n `).join("")}\n
\n `}return i?`
`:""}function R(t){const e=void 0!==t.history_days||void 0!==t.history_hours||void 0!==t.history_minutes,n=60*(60*(24*(e&&parseInt(t.history_days)||0)+(e&&parseInt(t.history_hours)||0))+(e?parseInt(t.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function H(t){const e=t/1e3;return e<=600?Math.ceil(e):Math.min(5e3,Math.ceil(e/5))}function W(t){return Math.max(500,Math.floor(t/5e3))}function O(t,e,n,i,o,a){t.has(e)||t.set(e,[]);const s=t.get(e);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function D(t,e,n=500){if(0===t.length)return t;t.sort((t,e)=>t.time-e.time);const i=[t[0]];for(let e=1;e=n&&i.push(t[e]);return i.length>e&&i.splice(0,i.length-e),i}function G(e,n,i,o,a,s,l,c){const{options:d,series:p}=function(e,n,i,o,a){i||(i=r[t]);const s=o?"140, 160, 220":"77, 217, 175",l=`rgb(${s})`,c=Date.now(),d=c-n,p=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(t=>t.time>=d).map(t=>[t.time,Math.abs(t.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:l}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:t=>i.format(t)},splitLine:{lineStyle:{opacity:.15}}};return p&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||!t.length)return"";const e=t[0];return`
${new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(e.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(l||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=n,u.options=d,u.data=p}function B(t,e,o,a,s){if(!t||!o||!e)return;const r=R(a);let l=0;for(const[,t]of Object.entries(o.circuits)){const n=t.entities?.power;if(!n)continue;const o=e.states[n],a=o&&parseFloat(o.state)||0;t.device_type!==i&&(l+=Math.abs(a))}!function(t,e,n,i,o){const a="current"===(i.chart_metric||"power"),s=t.querySelector(".stat-consumption .stat-value"),r=t.querySelector(".stat-consumption .stat-unit");if(a){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const t=n.panel_entities?.site_power;if(t){const n=e.states[t];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=v(o)),r&&(r.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),c=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",c&&(c.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=v(t),c&&(c.textContent="kW")}}const d=t.querySelector(".stat-downstream .stat-value"),p=t.querySelector(".stat-downstream .stat-unit");if(d){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=v(t),p&&(p.textContent="kW")}}const u=t.querySelector(".stat-solar .stat-value"),h=t.querySelector(".stat-solar .stat-unit");if(u){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(a){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);u.textContent=v(t)}else u.textContent="--";h&&(h.textContent="kW")}}const g=t.querySelector(".stat-battery .stat-value");if(g){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=t.querySelector(".stat-grid-state .stat-value");if(m){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;m.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(t,e,o,a,l);const d=$(a),p="current"===d.entityRole;for(const[a,l]of Object.entries(o.circuits)){const o=t.querySelector(`[data-uuid="${a}"]`);if(!o)continue;const u=l.entities?.power,h=u?e.states[u]:null,g=h&&parseFloat(h.state)||0,m=l.device_type===i||g<0,f=l.entities?.switch,v=f?e.states[f]:null,_=v?"on"===v.state:(h?.attributes?.relay_state||l.relay_state)===n,x=o.querySelector(".power-value");if(x)if(p){const t=l.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${y(g)}${b(g)}`;const w=o.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(_?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=_?"On":"Off")}o.classList.toggle("circuit-off",!_),o.classList.toggle("circuit-producer",m);const $=l.entities?.select,k=$?e.states[$]:null,S=k?k.state:"unknown",C=c[S]||c.unknown,E=o.querySelector(".shedding-icon");E&&(E.setAttribute("icon",C.icon),E.style.color=C.color,E.title=C.label);const T=o.querySelector(".chart-container");if(T){G(T,e,s.get(a)||[],r,d,m,o.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(t,e,n,i,o){if(!n.sub_devices)return;const a=R(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=q(s);if(r){const t=e.states[r],i=t&&parseFloat(t.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${y(i)} ${b(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const t of c){const n=t.dataset.chartKey,i=o.get(n)||[];let s=l.power;n.endsWith("_soc")?s=l.soc:n.endsWith("_soe")&&(s=l.soe);const r=!!t.closest(".bess-chart-col");G(t,e,i,a,s,!1,r?120:150)}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${t}"]`);if(!i)continue;const o=e.states[t];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function V(t,e,n){for(const{entityId:i,key:a}of function(t){if(!t.sub_devices)return[];const e=[];for(const[n,i]of Object.entries(t.sub_devices)){const t={power:q(i)};i.type===o&&(t.soc=A(i),t.soe=I(i));for(const[i,o]of Object.entries(t))o&&e.push({entityId:o,key:`${s}${n}_${i}`})}return e}(t))e.push(i),n.set(i,a)}async function X(t,e,n,i){if(!e||!t)return;const o=R(n),a=[],s=new Map;for(const[t,i]of Object.entries(e.circuits)){const e=k(i,n);e&&(a.push(e),s.set(e,t))}if(V(e,a,s),0===a.length)return;o>72e5?await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await t.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:e,period:s,types:["mean"]});for(const[t,e]of Object.entries(r)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=t.mean;if(null==e||!Number.isFinite(e))continue;const n=t.start;n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];e.sort((t,e)=>t.time-e.time),o.set(i,e)}}}(t,a,s,o,i):await async function(t,e,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await t.callWS({type:"history/history_during_period",start_time:a,entity_ids:e,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=H(i),l=W(i);for(const[t,e]of Object.entries(s)){const i=n.get(t);if(!i||!e)continue;const a=[];for(const t of e){const e=parseFloat(t.s);if(!Number.isFinite(e))continue;const n=1e3*(t.lu||t.lc||0);n>0&&a.push({time:n,value:e})}if(a.length>0){const t=o.get(i)||[],e=[...a,...t];o.set(i,D(e,r,l))}}}(t,a,s,o,i)}class K{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new S,this._updateInterval=null,this._hass=null,this._config=null}async render(t,e,n,i){this.stop(),this._hass=e,this._config=i;try{const t=await m(e,n);this._topology=t.topology,this._panelSize=t.panelSize}catch(e){return void(t.innerHTML=`

${e.message}

`)}await this._monitoringCache.fetch(e);const s=this._topology,r=Math.ceil(this._panelSize/2),l=(R(i),this._monitoringCache.status),c=function(t,e){const n=u(t.device_name||"SPAN Panel"),i=u(t.serial||""),o=u(t.firmware||""),a="current"===(e.chart_metric||"power");return`\n
\n
\n
\n

${n}

\n ${i}\n \n
\n
\n ${t.panel_entities?.site_power?`\n
\n Site\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.dsm_state?'\n
\n Grid\n
\n --\n
\n
':""}\n ${t.panel_entities?.current_power?`\n
\n Upstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.feedthrough_power?`\n
\n Downstream\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.pv_power?`\n
\n Solar\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${t.panel_entities?.battery_level?'\n
\n Battery\n
\n \n %\n
\n
':""}\n
\n
\n
\n ${o}\n
\n \n \n
\n
\n
\n `}(s,i),d=function(t){if(!t)return"";const e=Object.values(t.circuits||{}),n=Object.values(t.mains||{}),i=[...e,...n],o=i.filter(t=>t.utilization_pct>=80&&t.utilization_pct<100).length,a=i.filter(t=>t.utilization_pct>=100).length,s=i.filter(t=>t.has_override).length;return`\n
\n ✓ Monitoring · ${e.length} circuits · ${n.length} mains\n \n ${o>0?`${o} warning${o>1?"s":""}`:""}\n ${a>0?`${a} alert${a>1?"s":""}`:""}\n ${s>0?`${s} override${s>1?"s":""}`:""}\n \n
\n `}(l),p=function(t,e,n,i,o,a){const s=new Map,r=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),o=1===t.length?"single":w(t);s.set(i,{uuid:e,circuit:n,layout:o});for(const e of t)r.add(e)}const l=new Set,c=new Set;for(const[t,e]of s)if("col-span"===e.layout){const n=e.circuit.tabs,i=_(Math.max(...n));0===x(t)?l.add(i):c.add(i)}function d(t){const e=t.circuit.entities?.current||t.circuit.entities?.power,n=a?(o=a,s=e,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=t.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let t=1;t<=e;t++){const e=2*t-1,n=2*t,a=s.get(e),u=s.get(n);if(p+=`
${e}
`,a&&"row-span"===a.layout){const{monInfo:e,sheddingPriority:s}=d(a);p+=C(a.uuid,a.circuit,t,"2 / 4","row-span",0,i,o,e,s),p+=`
${n}
`;continue}if(!l.has(t))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(e)||(p+=E(t,"2"));else{const{monInfo:e,sheddingPriority:n}=d(a);p+=C(a.uuid,a.circuit,t,"2",a.layout,0,i,o,e,n)}if(!c.has(t))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=E(t,"3"));else{const{monInfo:e,sheddingPriority:n}=d(u);p+=C(u.uuid,u.circuit,t,"3",u.layout,0,i,o,e,n)}p+=`
${n}
`}return p}(s,r,0,e,i,l),h=function(t,e,n){const i=!1!==n.show_battery,s=!1!==n.show_evse;let r="";if(!t.sub_devices)return r;for(const[l,c]of Object.entries(t.sub_devices)){if(c.type===o&&!i)continue;if(c.type===a&&!s)continue;const t=c.type===a?"EV Charger":c.type===o?"Battery":"Sub-device",d=q(c),p=d?e.states[d]:null,h=p&&parseFloat(p.state)||0,g=c.type===o,m=g?A(c):null,f=g?I(c):null,v=g?j(c):null,_=F(c,e,n,new Set([d,m,f,v].filter(Boolean))),x=P(l,0,g,d,m,f);r+=`\n
\n
\n ${u(t)}\n ${u(c.name||"")}\n ${d?`${y(h)} ${b(h)}`:""}\n
\n ${x}\n ${_}\n
\n `}return r}(s,e,i);t.innerHTML=`\n \n ${c}\n ${d}\n ${!1!==i.show_panel?`\n
\n ${p}\n
\n `:""}\n ${h?`
${h}
`:""}\n \n `,this._bindGearClicks(t,s),this._bindToggleClicks(t,s),t.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await X(e,s,i,this._powerHistory)}catch{}B(t,e,s,i,this._powerHistory),U(t,e,s,i,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),B(t,this._hass,s,this._config,this._powerHistory),U(t,this._hass,s,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const t=R(this._config),e=H(t),n=W(t),i=Date.now(),o=i-t;for(const[t,a]of Object.entries(this._topology.circuits)){const s=k(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(t)||[];c.length>0&&i-c[c.length-1].time{const n=t.target.closest(".toggle-pill");if(!n)return;t.stopPropagation(),t.preventDefault();const i=n.closest("[data-uuid]");if(!i||!e||!this._hass)return;const o=i.dataset.uuid,a=e.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(t,e){t.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=t.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void t.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!e)return;const s=e.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const J="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Q="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Y="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",Z="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",tt="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function et(t,e,n,i,o){return`\n ${i}\n `}class nt{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(t,n,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const t={};this._configEntryId&&(t.config_entry_id=this._configEntryId);const i=await n.callWS({type:"call_service",domain:e,service:"get_monitoring_status",service_data:t,return_response:!0});o=i?.response||null}catch{o=null}const a=o?.global_settings||{},s=!0===o?.enabled,r=o?.circuits||{},l=o?.mains||{},c=new Set;for(const[t,e]of Object.entries(n.states||{})){if(!t.startsWith("person."))continue;const n=e.attributes?.device_trackers||[];for(const t of n){const e=t.split(".")[1];e&&c.add(`notify.mobile_app_${e}`)}}for(const t of Object.keys(n.states||{}))t.startsWith("notify.")&&c.add(t);for(const t of Object.keys(n.services?.notify||{}))c.add(`notify.${t}`);const d=[...c].sort(),p=a.notify_targets||"notify.notify",h=("string"==typeof p?p.split(","):p).map(t=>t.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,y=a.notification_priority||"default",v=Object.entries(r).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||"")),_=Object.entries(l),x=[...v,..._],w=x.length>0&&x.every(([,t])=>!1!==t.monitoring_enabled),$=x.some(([,t])=>!1!==t.monitoring_enabled),k=v.map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${et(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","circuit")}\n ${et(s,"spike_threshold_pct",e.spike_threshold_pct,"%","circuit")}\n ${et(s,"window_duration_m",e.window_duration_m,"m","circuit")}\n ${et(s,"cooldown_duration_m",e.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),S=Object.entries(l).map(([t,e])=>{const n=u(e.name||t),i=!1!==e.monitoring_enabled,o=!0===e.has_override,a=i?"":"opacity:0.4;",s=u(t);return`\n \n \n \n \n ${et(s,"continuous_threshold_pct",e.continuous_threshold_pct,"%","mains")}\n ${et(s,"spike_threshold_pct",e.spike_threshold_pct,"%","mains")}\n ${et(s,"window_duration_m",e.window_duration_m,"m","mains")}\n ${et(s,"cooldown_duration_m",e.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");t.innerHTML=`\n
\n

Monitoring

\n\n
\n
\n

Global Settings

\n \n
\n\n
\n
\n Continuous (%)\n \n
\n
\n Spike (%)\n \n
\n
\n Window (min)\n \n
\n
\n Cooldown (min)\n \n
\n\n
\n

Notification Settings

\n\n
\n Notify Targets\n
\n \n
\n ${0===d.length?'
No notify targets found
':d.map(t=>{const e=h.includes(t),i=n.states[t],o=i?.attributes?.friendly_name,a=o?`${u(o)} (${u(t)})`:u(t);return``}).join("")}\n
\n
\n
\n\n
\n Persistent Alerts\n \n
\n\n
\n Event Bus\n \n
\n\n
\n Priority\n \n \n ${"critical"===y?"Overrides silent/DND":"time-sensitive"===y?"Breaks through Focus":"passive"===y?"Delivers silently":"active"===y?"Standard delivery":""}\n \n
\n\n
\n Title Template\n \n
\n\n
\n Message Template\n \n
\n\n
\n Placeholders: {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

Monitored Points

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${S}\n ${k}\n \n
NameContinuousSpikeWindowCooldown
\n \n
\n
\n `;const C=t.querySelector("#toggle-all-circuits");C&&!w&&$&&(C.indeterminate=!0),this._bindGlobalControls(t,n),this._bindNotifyTargetSelect(t,n),this._bindNotificationSettings(t,n),this._bindToggleAll(t,n,r,l),this._bindCircuitToggles(t,n),this._bindMainsToggles(t,n),this._bindThresholdInputs(t,n),this._bindResetButtons(t,n)}_serviceData(t){return this._configEntryId&&(t.config_entry_id=this._configEntryId),t}_callSetGlobal(t,n){return t.callWS({type:"call_service",domain:e,service:"set_global_monitoring",service_data:this._serviceData({...n})})}_bindGlobalControls(t,e){const n=t.querySelector("#monitoring-enabled"),i=t.querySelector("#global-fields"),o=t.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(e,n),await this.render(t,e)}catch(t){o.textContent=`Error: ${t.message||"Failed to save"}`,o.style.color="var(--error-color, #f44336)"}},500)};n&&n.addEventListener("change",async()=>{const o=n.checked;i.style.opacity=o?"":"0.4",i.style.pointerEvents=o?"":"none";const a=t.querySelector("#global-status");try{if(o){const n={continuous_threshold_pct:parseInt(t.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(t.querySelector("#g-spike").value,10),window_duration_m:parseInt(t.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(t.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(e,n)}else await this._callSetGlobal(e,{enabled:!1})}catch(t){return void(a&&(a.textContent=`Error: ${t.message||"Failed"}`,a.style.color="var(--error-color, #f44336)"))}await this.render(t,e)});for(const e of t.querySelectorAll("#global-fields input[type=number]"))e.addEventListener("input",a)}_bindNotifyTargetSelect(t,e){const n=t.querySelector("#notify-target-btn"),i=t.querySelector("#notify-target-dropdown"),o=t.querySelector("#notify-target-label");if(!n||!i)return;n.addEventListener("click",t=>{t.stopPropagation();const e="none"!==i.style.display;i.style.display=e?"none":"block"});const a=e=>{const n=t.querySelector("#notify-target-select");n&&!n.contains(e.target)&&(i.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const n of t.querySelectorAll(".notify-target-cb"))n.addEventListener("change",()=>{const n=[...t.querySelectorAll(".notify-target-cb:checked")].map(t=>t.value);o.textContent=n.length?n.join(", "):"None selected",clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(e,{notify_targets:n.join(", ")})}catch{}},500)})}_bindNotificationSettings(t,e){const n=t.querySelector("#g-persistent-notifications"),i=t.querySelector("#g-event-bus"),o=t.querySelector("#g-priority"),a=t.querySelector("#g-title-template"),s=t.querySelector("#g-message-template"),r=(t,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(e,{[t]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(e,{notification_priority:o.value}),await this.render(t,e)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(t,n,i,o){const a=t.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(i).map(t=>n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:t,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(o).map(t=>n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:t,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(t,n)})}_bindMainsToggles(t,n){for(const i of t.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await Promise.all([n.callWS({type:"call_service",domain:e,service:"set_mains_threshold",service_data:this._serviceData({leg:o,monitoring_enabled:a})})])}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindCircuitToggles(t,n){for(const i of t.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const o=i.dataset.entity,a=i.checked;try{await n.callWS({type:"call_service",domain:e,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:o,monitoring_enabled:a})})}catch{return void(i.checked=!a)}await this.render(t,n)})}_bindThresholdInputs(t,n){const i=new Map;for(const o of t.querySelectorAll(".threshold-input"))o.addEventListener("input",()=>{const a=`${o.dataset.entity}-${o.dataset.field}`;clearTimeout(i.get(a)),i.set(a,setTimeout(async()=>{const i=parseInt(o.value,10);if(!i||i<1)return;const a=o.dataset.entity,s=o.dataset.field,r=o.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await n.callWS({type:"call_service",domain:e,service:l,service_data:this._serviceData({[c]:a,[s]:i})}),await this.render(t,n)}catch{o.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(t,n){for(const i of t.querySelectorAll(".reset-btn"))i.addEventListener("click",async()=>{const o=i.dataset.entity,a=i.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:o}:{circuit_id:o});await n.callService(e,s,r),await this.render(t,n)})}}class it{render(t,e){const n=e?`/config/integrations/integration/span_panel#config_entry=${e}`:"/config/integrations/integration/span_panel";t.innerHTML=`\n
\n

Settings

\n

\n General integration settings (entity naming, device prefix,\n circuit numbers) are managed through the integration's options flow.\n

\n \n Open SPAN Panel Integration Settings →\n \n
\n `}}class ot extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new K,this._monitoringTab=new nt,this._settingsTab=new it}set hass(t){this._hass=t,this._dashboardTab._hass=t,this._discovered||this._discoverPanels()}setConfig(t){this._config=t||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>t.identifiers?.some(t=>t[0]===e)&&!t.via_device_id);const n=localStorage.getItem("span_panel_selected");n&&this._panels.some(t=>t.id===n)?this._selectedPanelId=n:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){const t=this._panels.length>1,e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?e.name_by_user||e.name||e.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${t?`\n \n `:`${n}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const i=this.shadowRoot.getElementById("panel-select");i&&i.addEventListener("change",()=>{this._selectedPanelId=i.value,localStorage.setItem("span_panel_selected",i.value),this._renderTab()});for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.addEventListener("click",()=>{this._activeTab=t.dataset.tab;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",t=>{const e=t.target.closest(".unit-btn");if(!e)return;const n=e.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",t=>{const e=t.detail;if(e){this._activeTab=e;for(const t of this.shadowRoot.querySelectorAll(".panel-tab"))t.classList.toggle("active",t.dataset.tab===e);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const t=this.shadowRoot.getElementById("tab-content");if(t)switch(this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig();await this._dashboardTab.render(t,this._hass,this._selectedPanelId,e);break}case"monitoring":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;await this._monitoringTab.render(t,this._hass,n);break}case"settings":{t.innerHTML="";const e=this._panels.find(t=>t.id===this._selectedPanelId),n=e?.config_entries?.[0]||null;this._settingsTab.render(t,n);break}}}}customElements.define("span-panel",ot),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="span_panel",a="CLOSED",s="pv",r="bess",l="evse",c="sub_",d={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:d.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},g="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>h[e])}const f=Object.keys(u).filter(e=>"unknown"!==e);class _ extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of f){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const g=r?.continuous_threshold_pct??80,h=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",g,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",h,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=s,p.appendChild(u),p.appendChild(g),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(o,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function b(e,t){const i=await e.callWS({type:`${o}/panel_topology`,device_id:t}),a=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!a)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:a}}customElements.define("span-side-panel",_);const v=d.power;function y(e){return v.unit(e)}function x(e){return(e<0?"-":"")+v.format(e)}function w(e){return(Math.abs(e)/1e3).toFixed(1)}function $(e){return Math.ceil(e/2)}function S(e){return e%2==0?1:0}function k(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return $(t)===$(n)?"row-span":S(t)===S(n)?"col-span":"row-span"}function C(e){return d[e.chart_metric]||d[i]}function E(e,t){const n=function(e){return C(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,r,l,c,d,p,h){const f=t.entities?.power,_=f?c.states[f]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===s||b<0,w=t.entities?.switch,$=w?c.states[w]:null,S=$?"on"===$.state:(_?.attributes?.relay_state||t.relay_state)===a,k=t.breaker_rating_a,E=k?`${Math.round(k)}A`:"",P=m(t.name||n("grid.unknown")),A=C(d);let z;if("current"===A.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;z=`${A.format(i)}A`}else z=`${x(b)}${y(b)}`;const N=u[h||"unknown"]||u.unknown,M=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",q=``;let j="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);j=`${Math.round(e)}%`}const I=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${E?`${E}`:""}\n ${P}\n
\n
\n \n ${z}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${M}\n ${j}\n ${q}\n
\n
\n
\n `}function z(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},M={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return q(e,N)}function I(e){return q(e,M)}function F(e){return q(e,T)}function R(e){return q(e,L)}function D(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${m(l)}:\n ${m(d)}\n
\n `}return a}function H(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function B(e){return Math.max(500,Math.floor(e/5e3))}function G(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function V(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function U(e,t,n,o,a,s,r,l){const{options:c,series:p}=function(e,t,n,o,a){n||(n=d[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,p=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),g=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],h={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return p&&(h.min=n.fixedMin,h.max=n.fixedMax),a&&"current"===n.entityRole&&(h.min=0,h.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:h,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=p}function J(e,t,i,o,r){if(!e||!i||!t)return;const l=O(o);let c=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==s&&(c+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=w(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=w(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),g=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",g&&(g.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=w(e)}else u.textContent="--";g&&(g.textContent="kW")}}const h=e.querySelector(".stat-battery .stat-value");if(h){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(h.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,c);const d=C(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=c.entities?.power,h=g?t.states[g]:null,m=h&&parseFloat(h.state)||0,f=c.device_type===s||m<0,_=c.entities?.switch,b=_?t.states[_]:null,v=b?"on"===b.state:(h?.attributes?.relay_state||c.relay_state)===a,w=i.querySelector(".power-value");if(w)if(p){const e=c.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${d.format(i)}A`}else w.innerHTML=`${x(m)}${y(m)}`;const $=i.querySelector(".toggle-pill");if($){$.className="toggle-pill "+(v?"toggle-on":"toggle-off");const e=$.querySelector(".toggle-label");e&&(e.textContent=n(v?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!v),i.classList.toggle("circuit-producer",f);const S=c.entities?.select,k=S?t.states[S]:null,C=k?k.state:"unknown",E=u[C]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const A=i.querySelector(".chart-container");if(A){U(A,t,r.get(o)||[],l,d,f,i.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function X(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${x(i)} ${y(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=p.power;n.endsWith("_soc")?s=p.soc:n.endsWith("_soe")&&(s=p.soe);const r=!!e.closest(".bess-chart-col");U(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function K(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===r&&(e.soc=I(i),e.soe=F(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function Q(e,t,n,i){if(!t||!e)return;const o=O(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=E(i,n);t&&(a.push(t),s.set(t,e))}if(K(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),l=B(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,V(t,r,l))}}}(e,a,s,o,i)}class Y{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new P,this._updateInterval=null,this._hass=null,this._config=null}async render(e,t,i,o){this.stop(),this._hass=t,this._config=o;try{const e=await b(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t);const a=this._topology,s=Math.ceil(this._panelSize/2),c=(O(o),this._monitoringCache.status),d=function(e,t){const i=m(e.device_name||n("header.default_name")),o=m(e.serial||""),a=m(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(a,o),p=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),u=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":k(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=$(Math.max(...n));0===S(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=z(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=z(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(a,s,0,t,o,c),g=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[c,d]of Object.entries(e.sub_devices)){if(d.type===r&&!o)continue;if(d.type===l&&!a)continue;const e=d.type===l?n("subdevice.ev_charger"):d.type===r?n("subdevice.battery"):n("subdevice.fallback"),p=j(d),u=p?t.states[p]:null,g=u&&parseFloat(u.state)||0,h=d.type===r,f=h?I(d):null,_=h?F(d):null,b=h?R(d):null,v=D(d,t,i,new Set([p,f,_,b].filter(Boolean))),w=H(c,0,h,p,f,_);s+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${x(g)} ${y(g)}`:""}\n
\n ${w}\n ${v}\n
\n `}return s}(a,t,o);e.innerHTML=`\n \n ${d}\n ${p}\n ${!1!==o.show_panel?`\n
\n ${u}\n
\n `:""}\n ${g?`
${g}
`:""}\n \n `,this._bindGearClicks(e,a),this._bindToggleClicks(e,a),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await Q(t,a,o,this._powerHistory)}catch{}J(e,t,a,o,this._powerHistory),X(e,t,a,o,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),J(e,this._hass,a,this._config,this._powerHistory),X(e,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const e=O(this._config),t=W(e),n=B(e),i=Date.now(),o=i-e;for(const[e,a]of Object.entries(this._topology.circuits)){const s=E(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(e)||[];c.length>0&&i-c[c.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const Z="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",ee="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",te="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",ne="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ie="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function oe(e,t,n,i,o){return`\n ${i}\n `}class ae{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let a;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:e,return_response:!0});a=n?.response||null}catch{a=null}const s=a?.global_settings||{},r=!0===a?.enabled,l=a?.circuits||{},c=a?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=s.notify_targets||"notify.notify",g=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),h=s.notification_title_template||"SPAN: {name} {alert_type}",f=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),C=Object.entries(c).map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=g.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${m(o)} (${m(e)})`:m(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${C}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const E=e.querySelector("#toggle-all-circuits");E&&!$&&S&&(E.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:o,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const a=e.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:a})})])}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:a})})}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const a=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(a)),n.set(a,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const a=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:o,service:l,service_data:this._serviceData({[c]:a,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,a=n.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:i}:{circuit_id:i});await t.callService(o,s,r),await this.render(e,t)})}}class se{render(e,t){const i=t?`/config/integrations/integration/span_panel#config_entry=${t}`:"/config/integrations/integration/span_panel";e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n
\n `}}class re extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new Y,this._monitoringTab=new ae,this._settingsTab=new se}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===o)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;this._settingsTab.render(e,n);break}}}}customElements.define("span-panel",re),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/lefthook.yml b/lefthook.yml index 64f7d9d..b9d301a 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -11,3 +11,6 @@ pre-commit: - name: markdownlint glob: "*.md" run: npx markdownlint-cli2 {staged_files} + - name: i18n-validate + glob: "src/**/*.{js,mjs}" + run: node scripts/validate-i18n.mjs diff --git a/scripts/validate-i18n.mjs b/scripts/validate-i18n.mjs new file mode 100755 index 0000000..e77cadb --- /dev/null +++ b/scripts/validate-i18n.mjs @@ -0,0 +1,165 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ +/* global process */ +/** + * Validate that every t() key used in source files exists in all translation + * languages defined in src/i18n.js, and that English has no orphaned keys. + * + * Exit 0 on success, 1 on validation failure. + */ + +import { readFileSync, readdirSync, statSync } from "fs"; +import { join, relative } from "path"; + +const ROOT = new URL("..", import.meta.url).pathname.replace(/\/$/, ""); +const I18N_PATH = join(ROOT, "src", "i18n.js"); + +// ── Extract translation keys from i18n.js ────────────────────────────────── + +function extractTranslationKeys(source) { + // Match the top-level `const translations = { ... };` object. + // We parse it by importing as a module would be cleaner, but the file + // uses runtime imports (t depends on module state) so we regex-extract + // the key sets per language instead. + + const langBlocks = {}; + // Find each language block: ` en: {` ... ` },` + const langRe = /^\s{2}(\w+):\s*\{/gm; + let match; + while ((match = langRe.exec(source)) !== null) { + const lang = match[1]; + const startIdx = match.index + match[0].length; + // Walk forward counting braces to find the closing `}` + let depth = 1; + let i = startIdx; + while (i < source.length && depth > 0) { + if (source[i] === "{") depth++; + else if (source[i] === "}") depth--; + i++; + } + const block = source.slice(startIdx, i - 1); + // Extract all quoted keys: `"some.key":` + const keys = new Set(); + const keyRe = /"([^"]+)":/g; + let km; + while ((km = keyRe.exec(block)) !== null) { + keys.add(km[1]); + } + langBlocks[lang] = keys; + } + return langBlocks; +} + +// ── Scan source files for t() calls ──────────────────────────────────────── + +function collectSourceKeys(dir, results = new Map()) { + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + const stat = statSync(full); + if (stat.isDirectory()) { + if (entry === "node_modules" || entry === "dist") continue; + collectSourceKeys(full, results); + } else if (entry.endsWith(".js") && full !== I18N_PATH) { + const content = readFileSync(full, "utf8"); + // Match t("key") and t('key') — both template and regular strings + const re = /\bt\(\s*["']([^"']+)["']\s*\)/g; + let m; + while ((m = re.exec(content)) !== null) { + const key = m[1]; + if (!results.has(key)) results.set(key, []); + results.get(key).push(relative(ROOT, full)); + } + // Match t(`...`) with template literals containing i18n keys + const tmplRe = /\bt\(\s*`([^`]+)`\s*\)/g; + while ((m = tmplRe.exec(content)) !== null) { + // Template literals with ${} are dynamic — skip validation + if (!m[1].includes("${")) { + const key = m[1]; + if (!results.has(key)) results.set(key, []); + results.get(key).push(relative(ROOT, full)); + } + } + } + } + return results; +} + +// ── Main ─────────────────────────────────────────────────────────────────── + +const i18nSource = readFileSync(I18N_PATH, "utf8"); +const langKeys = extractTranslationKeys(i18nSource); +const languages = Object.keys(langKeys); +const enKeys = langKeys.en; + +if (!enKeys || enKeys.size === 0) { + console.error("ERROR: No English translation keys found in src/i18n.js"); + process.exit(1); +} + +const srcDir = join(ROOT, "src"); +const usedKeys = collectSourceKeys(srcDir); +let errors = []; + +// 1. Every t() key must exist in English +for (const [key, files] of usedKeys) { + if (!enKeys.has(key)) { + errors.push(` Missing in en: "${key}" (used in ${files[0]})`); + } +} + +// 2. Every English key must exist in all other languages +for (const lang of languages) { + if (lang === "en") continue; + const langSet = langKeys[lang]; + const missing = []; + for (const key of enKeys) { + if (!langSet.has(key)) { + missing.push(key); + } + } + if (missing.length > 0) { + errors.push(` ${lang}: ${missing.length} missing key(s) from en:`); + for (const key of missing) { + errors.push(` - ${key}`); + } + } +} + +// 3. No orphaned keys in non-English languages +for (const lang of languages) { + if (lang === "en") continue; + const langSet = langKeys[lang]; + const orphaned = []; + for (const key of langSet) { + if (!enKeys.has(key)) { + orphaned.push(key); + } + } + if (orphaned.length > 0) { + errors.push(` ${lang}: ${orphaned.length} orphaned key(s) not in en:`); + for (const key of orphaned) { + errors.push(` - ${key}`); + } + } +} + +// 4. Warn about unused English keys (not an error, but informational) +const unusedKeys = []; +for (const key of enKeys) { + if (!usedKeys.has(key)) { + unusedKeys.push(key); + } +} + +if (errors.length > 0) { + console.error("i18n validation failed:"); + for (const e of errors) console.error(e); + process.exit(1); +} + +if (unusedKeys.length > 0) { + console.warn(`i18n: ${unusedKeys.length} unused key(s) in en (not an error):`); + for (const key of unusedKeys) console.warn(` - ${key}`); +} + +console.log("i18n validation OK"); diff --git a/src/card/card-discovery.js b/src/card/card-discovery.js index 3571d5d..a778881 100644 --- a/src/card/card-discovery.js +++ b/src/card/card-discovery.js @@ -1,4 +1,5 @@ import { INTEGRATION_DOMAIN } from "../constants.js"; +import { t } from "../i18n.js"; // ── Primary discovery via custom WebSocket API ─────────────────────────────── @@ -10,7 +11,7 @@ export async function discoverTopology(hass, deviceId) { const panelSize = topology.panel_size || panelSizeFromCircuits(topology.circuits); if (!panelSize) { - throw new Error("Topology response missing panel_size and no circuits found. Update the SPAN Panel integration."); + throw new Error(t("card.topology_error")); } const devices = await hass.callWS({ type: "config/device_registry/list" }); @@ -120,7 +121,7 @@ export async function discoverEntitiesFallback(hass, deviceId) { panelSize = panelSizeFromCircuits(circuits); } if (!panelSize) { - throw new Error("Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration."); + throw new Error(t("card.panel_size_error")); } const subDeviceMap = {}; @@ -149,7 +150,7 @@ export async function discoverEntitiesFallback(hass, deviceId) { firmware: panelDevice.sw_version || "", panel_size: panelSize, device_id: deviceId, - device_name: panelDevice.name_by_user || panelDevice.name || "SPAN Panel", + device_name: panelDevice.name_by_user || panelDevice.name || t("header.default_name"), circuits, sub_devices: subDeviceMap, }; diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 78730ba..88e0444 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -1,4 +1,5 @@ import { DEFAULT_CHART_METRIC, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; +import { t } from "../i18n.js"; import { escapeHtml } from "../helpers/sanitize.js"; import { getHistoryDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; @@ -71,7 +72,7 @@ export class SpanPanelCard extends HTMLElement { this.shadowRoot.innerHTML = `
- Open the card editor and select your SPAN Panel device. + ${t("card.no_device")}
`; @@ -268,7 +269,7 @@ export class SpanPanelCard extends HTMLElement { _render() { const hass = this._hass; if (!hass || !this._topology || !this._panelSize) { - const msg = this._discoveryError || (!this._topology ? "Panel device not found. Check device_id in card config." : "Loading..."); + const msg = this._discoveryError || (!this._topology ? t("card.device_not_found") : t("card.loading")); this.shadowRoot.innerHTML = `
diff --git a/src/constants.js b/src/constants.js index 1b5c5a4..27ade76 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,3 +1,5 @@ +import { t } from "./i18n.js"; + export const CARD_VERSION = "0.8.9"; // ── Defaults ──────────────────────────────────────────────────────────────── @@ -22,13 +24,13 @@ export const SUB_DEVICE_KEY_PREFIX = "sub_"; export const CHART_METRICS = { power: { entityRole: "power", - label: "Power", + label: () => t("metric.power"), unit: v => (Math.abs(v) >= 1000 ? "kW" : "W"), format: v => (Math.abs(v) >= 1000 ? (Math.abs(v) / 1000).toFixed(1) : String(Math.round(Math.abs(v)))), }, current: { entityRole: "current", - label: "Current", + label: () => t("metric.current"), unit: () => "A", format: v => Math.abs(v).toFixed(1), }, @@ -36,14 +38,14 @@ export const CHART_METRICS = { export const BESS_CHART_METRICS = { soc: { - label: "State of Charge", + label: () => t("metric.soc"), unit: () => "%", format: v => String(Math.round(v)), fixedMin: 0, fixedMax: 100, }, soe: { - label: "State of Energy", + label: () => t("metric.soe"), unit: () => "kWh", format: v => v.toFixed(1), }, @@ -53,10 +55,10 @@ export const BESS_CHART_METRICS = { // ── Shedding priority ────────────────────────────────────────────────────── export const SHEDDING_PRIORITIES = { - never: { icon: "mdi:shield-check", color: "#4caf50", label: "Never" }, - soc_threshold: { icon: "mdi:battery-alert-variant-outline", color: "#9c27b0", label: "SoC Threshold" }, - off_grid: { icon: "mdi:transmission-tower", color: "#ff9800", label: "Off-Grid" }, - unknown: { icon: "mdi:help-circle-outline", color: "#888", label: "Unknown" }, + never: { icon: "mdi:shield-check", color: "#4caf50", label: () => t("shedding.never") }, + soc_threshold: { icon: "mdi:battery-alert-variant-outline", color: "#9c27b0", label: () => t("shedding.soc_threshold") }, + off_grid: { icon: "mdi:transmission-tower", color: "#ff9800", label: () => t("shedding.off_grid") }, + unknown: { icon: "mdi:help-circle-outline", color: "#888", label: () => t("shedding.unknown") }, }; export const MONITORING_COLORS = { diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index 67fcd0f..84c9537 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -1,5 +1,6 @@ import { BESS_CHART_METRICS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES } from "../constants.js"; import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; +import { t } from "../i18n.js"; import { getChartMetric } from "../helpers/chart.js"; import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; import { getHistoryDurationMs } from "../helpers/history.js"; @@ -153,7 +154,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory) { if (toggle) { toggle.className = `toggle-pill ${isOn ? "toggle-on" : "toggle-off"}`; const label = toggle.querySelector(".toggle-label"); - if (label) label.textContent = isOn ? "On" : "Off"; + if (label) label.textContent = isOn ? t("grid.on") : t("grid.off"); } slot.classList.toggle("circuit-off", !isOn); @@ -168,7 +169,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory) { if (sheddingIcon) { sheddingIcon.setAttribute("icon", shedInfo.icon); sheddingIcon.style.color = shedInfo.color; - sheddingIcon.title = shedInfo.label; + sheddingIcon.title = shedInfo.label(); } const chartContainer = slot.querySelector(".chart-container"); diff --git a/src/core/grid-renderer.js b/src/core/grid-renderer.js index fea5f2c..6d2f32d 100644 --- a/src/core/grid-renderer.js +++ b/src/core/grid-renderer.js @@ -1,5 +1,6 @@ import { escapeHtml } from "../helpers/sanitize.js"; import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; +import { t } from "../i18n.js"; import { tabToRow, tabToCol, classifyDualTab } from "../helpers/layout.js"; import { getChartMetric } from "../helpers/chart.js"; import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES, MONITORING_COLORS } from "../constants.js"; @@ -114,7 +115,7 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, const breakerAmps = circuit.breaker_rating_a; const breakerLabel = breakerAmps ? `${Math.round(breakerAmps)}A` : ""; - const name = escapeHtml(circuit.name || "Unknown"); + const name = escapeHtml(circuit.name || t("grid.unknown")); const chartMetric = getChartMetric(config); const showCurrent = chartMetric.entityRole === "current"; @@ -134,14 +135,14 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, const sheddingHTML = ``; + title="${shedInfo.label()}">`; // Gear icon const hasOverridesFlag = monitoringInfo && hasCustomOverrides(monitoringInfo); const gearColor = hasOverridesFlag ? MONITORING_COLORS.custom : "#555"; const gearHTML = ``; @@ -178,7 +179,7 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, circuit.is_user_controllable !== false && circuit.entities?.switch ? `
- ${isOn ? "On" : "Off"} + ${isOn ? t("grid.on") : t("grid.off")}
` diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index dc2027a..246105f 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -1,4 +1,5 @@ import { escapeHtml } from "../helpers/sanitize.js"; +import { t } from "../i18n.js"; /** * Build the panel header HTML with stats, gear icon, and A/W toggle. @@ -7,7 +8,7 @@ import { escapeHtml } from "../helpers/sanitize.js"; * @returns {string} HTML string */ export function buildHeaderHTML(topology, config) { - const panelName = escapeHtml(topology.device_name || "SPAN Panel"); + const panelName = escapeHtml(topology.device_name || t("header.default_name")); const serial = escapeHtml(topology.serial || ""); const firmware = escapeHtml(topology.firmware || ""); const isAmpsMode = (config.chart_metric || "power") === "current"; @@ -25,7 +26,7 @@ export function buildHeaderHTML(topology, config) {

${panelName}

${serial} -
@@ -34,7 +35,7 @@ export function buildHeaderHTML(topology, config) { hasSite ? `
- Site + ${t("header.site")}
0 ${isAmpsMode ? "A" : "kW"} @@ -46,7 +47,7 @@ export function buildHeaderHTML(topology, config) { hasGrid ? `
- Grid + ${t("header.grid")}
--
@@ -57,7 +58,7 @@ export function buildHeaderHTML(topology, config) { hasUpstream ? `
- Upstream + ${t("header.upstream")}
-- ${isAmpsMode ? "A" : "kW"} @@ -69,7 +70,7 @@ export function buildHeaderHTML(topology, config) { hasDownstream ? `
- Downstream + ${t("header.downstream")}
-- ${isAmpsMode ? "A" : "kW"} @@ -81,7 +82,7 @@ export function buildHeaderHTML(topology, config) { hasSolar ? `
- Solar + ${t("header.solar")}
-- ${isAmpsMode ? "A" : "kW"} @@ -93,7 +94,7 @@ export function buildHeaderHTML(topology, config) { hasBattery ? `
- Battery + ${t("header.battery")}
% @@ -105,7 +106,7 @@ export function buildHeaderHTML(topology, config) {
${firmware} -
+
diff --git a/src/core/monitoring-status.js b/src/core/monitoring-status.js index ea8ae50..dab4c07 100644 --- a/src/core/monitoring-status.js +++ b/src/core/monitoring-status.js @@ -1,5 +1,6 @@ // src/core/monitoring-status.js import { INTEGRATION_DOMAIN } from "../constants.js"; +import { t } from "../i18n.js"; const MONITORING_POLL_INTERVAL_MS = 30_000; @@ -135,11 +136,11 @@ export function buildMonitoringSummaryHTML(status) { return `
- ✓ Monitoring · ${circuits.length} circuits · ${mains.length} mains + ✓ ${t("status.monitoring")} · ${circuits.length} ${t("status.circuits")} · ${mains.length} ${t("status.mains")} - ${warnings > 0 ? `${warnings} warning${warnings > 1 ? "s" : ""}` : ""} - ${alerts > 0 ? `${alerts} alert${alerts > 1 ? "s" : ""}` : ""} - ${overrides > 0 ? `${overrides} override${overrides > 1 ? "s" : ""}` : ""} + ${warnings > 0 ? `${warnings} ${warnings > 1 ? t("status.warnings") : t("status.warning")}` : ""} + ${alerts > 0 ? `${alerts} ${alerts > 1 ? t("status.alerts") : t("status.alert")}` : ""} + ${overrides > 0 ? `${overrides} ${overrides > 1 ? t("status.overrides") : t("status.override")}` : ""}
`; diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 9928f88..d14c2fe 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -1,6 +1,7 @@ // src/core/side-panel.js import { escapeHtml } from "../helpers/sanitize.js"; import { INTEGRATION_DOMAIN, SHEDDING_PRIORITIES } from "../constants.js"; +import { t } from "../i18n.js"; const DEBOUNCE_MS = 500; @@ -235,7 +236,7 @@ class SpanSidePanel extends HTMLElement { } _renderPanelMode(panel) { - const header = this._createHeader("Panel Monitoring", "Global defaults for all circuits"); + const header = this._createHeader(t("sidepanel.panel_monitoring"), t("sidepanel.global_defaults")); panel.appendChild(header); const body = document.createElement("div"); @@ -244,15 +245,13 @@ class SpanSidePanel extends HTMLElement { const info = document.createElement("div"); info.className = "panel-mode-info"; info.innerHTML = ` -

Global monitoring thresholds apply to all circuits that don't have custom overrides. - Use the integration's options flow to change global settings.

-

Individual circuit thresholds can be configured by clicking the gear icon on a circuit row - and switching to Custom mode.

+

${t("sidepanel.global_info")}

+

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

`; body.appendChild(info); const link = document.createElement("button"); - link.textContent = "Configure Global Thresholds"; + link.textContent = t("sidepanel.configure_global"); Object.assign(link.style, { display: "inline-block", marginTop: "8px", @@ -318,14 +317,14 @@ class SpanSidePanel extends HTMLElement { const section = document.createElement("div"); section.className = "section"; - section.innerHTML = ``; + section.innerHTML = ``; const row = document.createElement("div"); row.className = "field-row"; const label = document.createElement("span"); label.className = "field-label"; - label.textContent = "Breaker"; + label.textContent = t("sidepanel.breaker"); const toggle = document.createElement("ha-switch"); toggle.dataset.role = "relay-toggle"; @@ -338,7 +337,7 @@ class SpanSidePanel extends HTMLElement { toggle.addEventListener("change", () => { const isOn = toggle.hasAttribute("checked") || toggle.checked; this._callService("switch", isOn ? "turn_on" : "turn_off", { entity_id: entityId }).catch(err => - this._showError(`Relay toggle failed: ${err.message ?? err}`) + this._showError(`${t("sidepanel.relay_failed")} ${err.message ?? err}`) ); }); @@ -355,14 +354,14 @@ class SpanSidePanel extends HTMLElement { const section = document.createElement("div"); section.className = "section"; - section.innerHTML = ``; + section.innerHTML = ``; const row = document.createElement("div"); row.className = "field-row"; const label = document.createElement("span"); label.className = "field-label"; - label.textContent = "Priority"; + label.textContent = t("sidepanel.priority_label"); const selectEl = document.createElement("select"); selectEl.dataset.role = "shedding-select"; @@ -372,7 +371,7 @@ class SpanSidePanel extends HTMLElement { for (const key of PRIORITY_OPTIONS) { const opt = document.createElement("option"); opt.value = key; - opt.textContent = SHEDDING_PRIORITIES[key].label; + opt.textContent = SHEDDING_PRIORITIES[key].label(); if (key === currentPriority) opt.selected = true; selectEl.appendChild(opt); } @@ -381,7 +380,7 @@ class SpanSidePanel extends HTMLElement { this._callService("select", "select_option", { entity_id: entityId, option: selectEl.value, - }).catch(err => this._showError(`Shedding update failed: ${err.message ?? err}`)); + }).catch(err => this._showError(`${t("sidepanel.shedding_failed")} ${err.message ?? err}`)); }); row.appendChild(label); @@ -401,7 +400,7 @@ class SpanSidePanel extends HTMLElement { const sectionLabel = document.createElement("div"); sectionLabel.className = "section-label"; - sectionLabel.textContent = "Monitoring"; + sectionLabel.textContent = t("sidepanel.monitoring"); sectionLabel.style.margin = "0"; const enableToggle = document.createElement("ha-switch"); @@ -428,8 +427,8 @@ class SpanSidePanel extends HTMLElement { const radioGroup = document.createElement("div"); radioGroup.className = "radio-group"; radioGroup.innerHTML = ` - - + + `; detailsWrap.appendChild(radioGroup); @@ -443,10 +442,10 @@ class SpanSidePanel extends HTMLElement { const windowVal = info?.window_duration_m ?? 15; const cooldownVal = info?.cooldown_duration_m ?? 15; - thresholdsWrap.appendChild(this._createThresholdRow("Continuous %", "continuous", continuousVal, cfg)); - thresholdsWrap.appendChild(this._createThresholdRow("Spike %", "spike", spikeVal, cfg)); - thresholdsWrap.appendChild(this._createDurationRow("Window duration", "window-m", windowVal, 1, 180, "m", cfg)); - thresholdsWrap.appendChild(this._createDurationRow("Cooldown", "cooldown-m", cooldownVal, 1, 180, "m", cfg)); + thresholdsWrap.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"), "continuous", continuousVal, cfg)); + thresholdsWrap.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"), "spike", spikeVal, cfg)); + thresholdsWrap.appendChild(this._createDurationRow(t("sidepanel.window_duration"), "window-m", windowVal, 1, 180, "m", cfg)); + thresholdsWrap.appendChild(this._createDurationRow(t("sidepanel.cooldown"), "cooldown-m", cooldownVal, 1, 180, "m", cfg)); detailsWrap.appendChild(thresholdsWrap); // Event: monitoring enable toggle @@ -457,7 +456,7 @@ class SpanSidePanel extends HTMLElement { this._callDomainService("set_circuit_threshold", { circuit_id: entityId, monitoring_enabled: checked, - }).catch(err => this._showError(`Monitoring toggle failed: ${err.message ?? err}`)); + }).catch(err => this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${err.message ?? err}`)); }); // Event: radio change @@ -469,7 +468,7 @@ class SpanSidePanel extends HTMLElement { if (!isCustom && radio.checked) { const entityId = cfg.entities?.power || cfg.uuid; this._callDomainService("clear_circuit_threshold", { circuit_id: entityId }).catch(err => - this._showError(`Clear monitoring failed: ${err.message ?? err}`) + this._showError(`${t("sidepanel.clear_monitoring_failed")} ${err.message ?? err}`) ); } }); @@ -506,7 +505,7 @@ class SpanSidePanel extends HTMLElement { spike_threshold_pct: spike ? Number(spike.value) : undefined, window_duration_m: windowM ? Number(windowM.value) : undefined, cooldown_duration_m: cooldownM ? Number(cooldownM.value) : undefined, - }).catch(err => this._showError(`Save threshold failed: ${err.message ?? err}`)); + }).catch(err => this._showError(`${t("sidepanel.save_threshold_failed")} ${err.message ?? err}`)); }); }); @@ -552,7 +551,7 @@ class SpanSidePanel extends HTMLElement { continuous_threshold_pct: continuous ? Number(continuous.value) : undefined, spike_threshold_pct: spike ? Number(spike.value) : undefined, window_duration_m: windowM ? Number(windowM.value) : undefined, - }).catch(err => this._showError(`Save threshold failed: ${err.message ?? err}`)); + }).catch(err => this._showError(`${t("sidepanel.save_threshold_failed")} ${err.message ?? err}`)); }); }); } diff --git a/src/core/sub-device-renderer.js b/src/core/sub-device-renderer.js index 8be46a5..ddc53c4 100644 --- a/src/core/sub-device-renderer.js +++ b/src/core/sub-device-renderer.js @@ -1,5 +1,6 @@ import { escapeHtml } from "../helpers/sanitize.js"; import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; +import { t } from "../i18n.js"; import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity, findBatteryCapacityEntity } from "../helpers/entity-finder.js"; import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_TYPE_EVSE, SUB_DEVICE_KEY_PREFIX } from "../constants.js"; @@ -23,7 +24,8 @@ export function buildSubDevicesHTML(topology, hass, config, _durationMs) { if (sub.type === SUB_DEVICE_TYPE_BESS && !showBattery) continue; if (sub.type === SUB_DEVICE_TYPE_EVSE && !showEvse) continue; - const label = sub.type === SUB_DEVICE_TYPE_EVSE ? "EV Charger" : sub.type === SUB_DEVICE_TYPE_BESS ? "Battery" : "Sub-device"; + const label = + sub.type === SUB_DEVICE_TYPE_EVSE ? t("subdevice.ev_charger") : sub.type === SUB_DEVICE_TYPE_BESS ? t("subdevice.battery") : t("subdevice.fallback"); const powerEid = findSubDevicePowerEntity(sub); const powerState = powerEid ? hass.states[powerEid] : null; const powerW = powerState ? parseFloat(powerState.state) || 0 : 0; @@ -111,9 +113,9 @@ export function buildSubEntityHTML(sub, hass, config, hideEids) { export function buildSubDeviceChartsHTML(devId, _sub, isBess, powerEid, battLevelEid, battSoeEid) { if (isBess) { const bessCharts = [ - { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soc`, title: "SoC", available: !!battLevelEid }, - { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soe`, title: "SoE", available: !!battSoeEid }, - { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_power`, title: "Power", available: !!powerEid }, + { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soc`, title: t("subdevice.soc"), available: !!battLevelEid }, + { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soe`, title: t("subdevice.soe"), available: !!battSoeEid }, + { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_power`, title: t("subdevice.power"), available: !!powerEid }, ].filter(c => c.available); return ` diff --git a/src/editor/span-panel-card-editor.js b/src/editor/span-panel-card-editor.js index cb76344..284d487 100644 --- a/src/editor/span-panel-card-editor.js +++ b/src/editor/span-panel-card-editor.js @@ -1,4 +1,5 @@ import { CHART_METRICS, DEFAULT_CHART_METRIC, INTEGRATION_DOMAIN } from "../constants.js"; +import { t } from "../i18n.js"; export class SpanPanelCardEditor extends HTMLElement { constructor() { @@ -31,7 +32,7 @@ export class SpanPanelCardEditor extends HTMLElement { .filter(d => (d.identifiers || []).some(pair => pair[0] === INTEGRATION_DOMAIN) && !d.via_device_id) .map(d => { const serial = (d.identifiers || []).find(p => p[0] === INTEGRATION_DOMAIN)?.[1] || ""; - const name = d.name_by_user || d.name || "SPAN Panel"; + const name = d.name_by_user || d.name || t("editor.panel_label"); return { device_id: d.id, label: `${name} (${serial})` }; }); this._buildEditor(); @@ -76,14 +77,14 @@ export class SpanPanelCardEditor extends HTMLElement { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); - label.textContent = "SPAN Panel"; + label.textContent = t("editor.panel_label"); label.style.cssText = labelStyle; const select = document.createElement("select"); select.style.cssText = fieldStyle; const emptyOpt = document.createElement("option"); emptyOpt.value = ""; - emptyOpt.textContent = "Select a panel..."; + emptyOpt.textContent = t("editor.select_panel"); select.appendChild(emptyOpt); if (this._panels) { @@ -112,7 +113,7 @@ export class SpanPanelCardEditor extends HTMLElement { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); - label.textContent = "Chart time window"; + label.textContent = t("editor.chart_window"); label.style.cssText = labelStyle; const row = document.createElement("div"); @@ -141,9 +142,9 @@ export class SpanPanelCardEditor extends HTMLElement { const daysValue = parseInt(this._config.history_days) || 0; const hoursValue = parseInt(this._config.history_hours) || 0; const minsValue = parseInt(this._config.history_minutes) || 0; - const days = createTimeField(daysValue, "0", "30", "days"); - const hours = createTimeField(hoursValue, "0", "23", "hours"); - const mins = createTimeField(minsValue, "0", "59", "minutes"); + const days = createTimeField(daysValue, "0", "30", t("editor.days")); + const hours = createTimeField(hoursValue, "0", "23", t("editor.hours")); + const mins = createTimeField(minsValue, "0", "59", t("editor.minutes")); const fireTimeChange = () => { this._config = { @@ -174,7 +175,7 @@ export class SpanPanelCardEditor extends HTMLElement { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); - label.textContent = "Chart metric"; + label.textContent = t("editor.chart_metric"); label.style.cssText = labelStyle; const select = document.createElement("select"); select.style.cssText = fieldStyle; @@ -194,7 +195,7 @@ export class SpanPanelCardEditor extends HTMLElement { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); - label.textContent = "Visible sections"; + label.textContent = t("editor.visible_sections"); label.style.cssText = labelStyle; group.appendChild(label); @@ -202,9 +203,9 @@ export class SpanPanelCardEditor extends HTMLElement { const cbLabelStyle = "font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;"; const sections = [ - { key: "show_panel", label: "Panel circuits", subDeviceType: null }, - { key: "show_battery", label: "Battery (BESS)", subDeviceType: "bess" }, - { key: "show_evse", label: "EV Charger (EVSE)", subDeviceType: "evse" }, + { key: "show_panel", label: t("editor.panel_circuits"), subDeviceType: null }, + { key: "show_battery", label: t("editor.battery_bess"), subDeviceType: "bess" }, + { key: "show_evse", label: t("editor.ev_charger_evse"), subDeviceType: "evse" }, ]; this._checkboxes = {}; @@ -332,7 +333,7 @@ export class SpanPanelCardEditor extends HTMLElement { if (this._availableRoles && !this._availableRoles.has(def.entityRole)) continue; const opt = document.createElement("option"); opt.value = key; - opt.textContent = def.label; + opt.textContent = def.label(); if (key === current) opt.selected = true; select.appendChild(opt); } diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000..3b79680 --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,683 @@ +/** + * Internationalization module for the SPAN Panel card. + * + * Usage: + * import { setLanguage, t } from "../i18n.js"; + * setLanguage(hass.language); // call once per render cycle + * t("monitoring.heading"); // => "Monitoring" (or translated equivalent) + */ + +let _lang = "en"; + +const translations = { + en: { + // Tab labels + "tab.panel": "Panel", + "tab.monitoring": "Monitoring", + "tab.settings": "Settings", + + // Monitoring tab + "monitoring.heading": "Monitoring", + "monitoring.global_settings": "Global Settings", + "monitoring.enabled": "Enabled", + "monitoring.continuous": "Continuous (%)", + "monitoring.spike": "Spike (%)", + "monitoring.window": "Window (min)", + "monitoring.cooldown": "Cooldown (min)", + "monitoring.monitored_points": "Monitored Points", + "monitoring.col.name": "Name", + "monitoring.col.continuous": "Continuous", + "monitoring.col.spike": "Spike", + "monitoring.col.window": "Window", + "monitoring.col.cooldown": "Cooldown", + "monitoring.all_none": "All / None", + "monitoring.reset": "Reset", + + // Notification settings + "notification.heading": "Notification Settings", + "notification.targets": "Notify Targets", + "notification.none_selected": "None selected", + "notification.no_targets": "No notify targets found", + "notification.persistent": "Persistent Alerts", + "notification.persistent_hint": "Create persistent HA notifications", + "notification.event_bus": "Event Bus", + "notification.event_bus_hint": "Fire events on the HA event bus", + "notification.priority": "Priority", + "notification.priority.default": "Default", + "notification.priority.passive": "Passive", + "notification.priority.active": "Active", + "notification.priority.time_sensitive": "Time-sensitive", + "notification.priority.critical": "Critical", + "notification.hint.critical": "Overrides silent/DND", + "notification.hint.time_sensitive": "Breaks through Focus", + "notification.hint.passive": "Delivers silently", + "notification.hint.active": "Standard delivery", + "notification.title_template": "Title Template", + "notification.message_template": "Message Template", + "notification.placeholders": "Placeholders:", + + // Error messages + "error.prefix": "Error:", + "error.failed_save": "Failed to save", + "error.failed": "Failed", + + // Settings tab + "settings.heading": "Settings", + "settings.description": "General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.", + "settings.open_link": "Open SPAN Panel Integration Settings", + + // Header + "header.default_name": "SPAN Panel", + "header.monitoring_settings": "Panel monitoring settings", + "header.site": "Site", + "header.grid": "Grid", + "header.upstream": "Upstream", + "header.downstream": "Downstream", + "header.solar": "Solar", + "header.battery": "Battery", + "header.toggle_units": "Toggle Watts / Amps", + + // Grid + "grid.unknown": "Unknown", + "grid.configure": "Configure circuit", + "grid.on": "On", + "grid.off": "Off", + + // Sub-devices + "subdevice.ev_charger": "EV Charger", + "subdevice.battery": "Battery", + "subdevice.fallback": "Sub-device", + "subdevice.soc": "SoC", + "subdevice.soe": "SoE", + "subdevice.power": "Power", + + // Side panel + "sidepanel.panel_monitoring": "Panel Monitoring", + "sidepanel.global_defaults": "Global defaults for all circuits", + "sidepanel.global_info": + "Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.", + "sidepanel.custom_info_prefix": "Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to", + "sidepanel.custom_info_suffix": "mode.", + "sidepanel.configure_global": "Configure Global Thresholds", + "sidepanel.relay": "Relay", + "sidepanel.breaker": "Breaker", + "sidepanel.relay_failed": "Relay toggle failed:", + "sidepanel.shedding_priority": "Shedding Priority", + "sidepanel.priority_label": "Priority", + "sidepanel.shedding_failed": "Shedding update failed:", + "sidepanel.monitoring": "Monitoring", + "sidepanel.global": "Global", + "sidepanel.custom": "Custom", + "sidepanel.continuous_pct": "Continuous %", + "sidepanel.spike_pct": "Spike %", + "sidepanel.window_duration": "Window duration", + "sidepanel.cooldown": "Cooldown", + "sidepanel.monitoring_toggle_failed": "Monitoring toggle failed:", + "sidepanel.clear_monitoring_failed": "Clear monitoring failed:", + "sidepanel.save_threshold_failed": "Save threshold failed:", + + // Monitoring status bar + "status.monitoring": "Monitoring", + "status.circuits": "circuits", + "status.mains": "mains", + "status.warning": "warning", + "status.warnings": "warnings", + "status.alert": "alert", + "status.alerts": "alerts", + "status.override": "override", + "status.overrides": "overrides", + + // Card + "card.no_device": "Open the card editor and select your SPAN Panel device.", + "card.device_not_found": "Panel device not found. Check device_id in card config.", + "card.loading": "Loading...", + "card.topology_error": "Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.", + "card.panel_size_error": "Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.", + + // Editor + "editor.panel_label": "SPAN Panel", + "editor.select_panel": "Select a panel...", + "editor.chart_window": "Chart time window", + "editor.days": "days", + "editor.hours": "hours", + "editor.minutes": "minutes", + "editor.chart_metric": "Chart metric", + "editor.visible_sections": "Visible sections", + "editor.panel_circuits": "Panel circuits", + "editor.battery_bess": "Battery (BESS)", + "editor.ev_charger_evse": "EV Charger (EVSE)", + + // Metrics and shedding (used in constants) + "metric.power": "Power", + "metric.current": "Current", + "metric.soc": "State of Charge", + "metric.soe": "State of Energy", + "shedding.never": "Never", + "shedding.soc_threshold": "SoC Threshold", + "shedding.off_grid": "Off-Grid", + "shedding.unknown": "Unknown", + }, + + es: { + "tab.panel": "Panel", + "tab.monitoring": "Monitoreo", + "tab.settings": "Configuraci\u00f3n", + "monitoring.heading": "Monitoreo", + "monitoring.global_settings": "Configuraci\u00f3n Global", + "monitoring.enabled": "Activado", + "monitoring.continuous": "Continuo (%)", + "monitoring.spike": "Pico (%)", + "monitoring.window": "Ventana (min)", + "monitoring.cooldown": "Enfriamiento (min)", + "monitoring.monitored_points": "Puntos Monitoreados", + "monitoring.col.name": "Nombre", + "monitoring.col.continuous": "Continuo", + "monitoring.col.spike": "Pico", + "monitoring.col.window": "Ventana", + "monitoring.col.cooldown": "Enfriamiento", + "monitoring.all_none": "Todos / Ninguno", + "monitoring.reset": "Restablecer", + "notification.heading": "Configuraci\u00f3n de Notificaciones", + "notification.targets": "Destinos de Notificaci\u00f3n", + "notification.none_selected": "Ninguno seleccionado", + "notification.no_targets": "No se encontraron destinos de notificaci\u00f3n", + "notification.persistent": "Alertas Persistentes", + "notification.persistent_hint": "Crear notificaciones persistentes en HA", + "notification.event_bus": "Bus de Eventos", + "notification.event_bus_hint": "Emitir eventos en el bus de eventos de HA", + "notification.priority": "Prioridad", + "notification.priority.default": "Predeterminado", + "notification.priority.passive": "Pasivo", + "notification.priority.active": "Activo", + "notification.priority.time_sensitive": "Urgente", + "notification.priority.critical": "Cr\u00edtico", + "notification.hint.critical": "Anula silencio/No molestar", + "notification.hint.time_sensitive": "Atraviesa el modo Concentraci\u00f3n", + "notification.hint.passive": "Entrega silenciosa", + "notification.hint.active": "Entrega est\u00e1ndar", + "notification.title_template": "Plantilla de T\u00edtulo", + "notification.message_template": "Plantilla de Mensaje", + "notification.placeholders": "Variables:", + "error.prefix": "Error:", + "error.failed_save": "Error al guardar", + "error.failed": "Fall\u00f3", + "settings.heading": "Configuraci\u00f3n", + "settings.description": + "La configuraci\u00f3n general de la integraci\u00f3n (nombres de entidades, prefijo de dispositivo, n\u00fameros de circuito) se administra a trav\u00e9s del flujo de opciones de la integraci\u00f3n.", + "settings.open_link": "Abrir Configuraci\u00f3n de Integraci\u00f3n SPAN Panel", + "header.default_name": "SPAN Panel", + "header.monitoring_settings": "Configuraci\u00f3n de monitoreo del panel", + "header.site": "Sitio", + "header.grid": "Red", + "header.upstream": "Aguas arriba", + "header.downstream": "Aguas abajo", + "header.solar": "Solar", + "header.battery": "Bater\u00eda", + "header.toggle_units": "Alternar Watts / Amperios", + "grid.unknown": "Desconocido", + "grid.configure": "Configurar circuito", + "grid.on": "Enc", + "grid.off": "Apag", + "subdevice.ev_charger": "Cargador EV", + "subdevice.battery": "Bater\u00eda", + "subdevice.fallback": "Sub-dispositivo", + "subdevice.soc": "SoC", + "subdevice.soe": "SoE", + "subdevice.power": "Potencia", + "sidepanel.panel_monitoring": "Monitoreo del Panel", + "sidepanel.global_defaults": "Valores predeterminados globales para todos los circuitos", + "sidepanel.global_info": + "Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integraci\u00f3n para cambiar la configuraci\u00f3n global.", + "sidepanel.custom_info_prefix": + "Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo", + "sidepanel.custom_info_suffix": ".", + "sidepanel.configure_global": "Configurar Umbrales Globales", + "sidepanel.relay": "Rel\u00e9", + "sidepanel.breaker": "Interruptor", + "sidepanel.relay_failed": "Error al cambiar rel\u00e9:", + "sidepanel.shedding_priority": "Prioridad de Desconexci\u00f3n", + "sidepanel.priority_label": "Prioridad", + "sidepanel.shedding_failed": "Error al actualizar desconexci\u00f3n:", + "sidepanel.monitoring": "Monitoreo", + "sidepanel.global": "Global", + "sidepanel.custom": "Personalizado", + "sidepanel.continuous_pct": "Continuo %", + "sidepanel.spike_pct": "Pico %", + "sidepanel.window_duration": "Duraci\u00f3n de ventana", + "sidepanel.cooldown": "Enfriamiento", + "sidepanel.monitoring_toggle_failed": "Error al cambiar monitoreo:", + "sidepanel.clear_monitoring_failed": "Error al limpiar monitoreo:", + "sidepanel.save_threshold_failed": "Error al guardar umbral:", + "status.monitoring": "Monitoreo", + "status.circuits": "circuitos", + "status.mains": "alimentaci\u00f3n", + "status.warning": "advertencia", + "status.warnings": "advertencias", + "status.alert": "alerta", + "status.alerts": "alertas", + "status.override": "anulaci\u00f3n", + "status.overrides": "anulaciones", + "card.no_device": "Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.", + "card.device_not_found": "Dispositivo de panel no encontrado. Verifique device_id en la configuraci\u00f3n de la tarjeta.", + "card.loading": "Cargando...", + "card.topology_error": "La respuesta de topolog\u00eda no contiene panel_size y no se encontraron circuitos. Actualice la integraci\u00f3n SPAN Panel.", + "card.panel_size_error": "No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integraci\u00f3n SPAN Panel.", + "editor.panel_label": "SPAN Panel", + "editor.select_panel": "Seleccione un panel...", + "editor.chart_window": "Ventana de tiempo del gr\u00e1fico", + "editor.days": "d\u00edas", + "editor.hours": "horas", + "editor.minutes": "minutos", + "editor.chart_metric": "M\u00e9trica del gr\u00e1fico", + "editor.visible_sections": "Secciones visibles", + "editor.panel_circuits": "Circuitos del panel", + "editor.battery_bess": "Bater\u00eda (BESS)", + "editor.ev_charger_evse": "Cargador EV (EVSE)", + "metric.power": "Potencia", + "metric.current": "Corriente", + "metric.soc": "Estado de Carga", + "metric.soe": "Estado de Energ\u00eda", + "shedding.never": "Nunca", + "shedding.soc_threshold": "Umbral SoC", + "shedding.off_grid": "Fuera de Red", + "shedding.unknown": "Desconocido", + }, + + fr: { + "tab.panel": "Panneau", + "tab.monitoring": "Surveillance", + "tab.settings": "Param\u00e8tres", + "monitoring.heading": "Surveillance", + "monitoring.global_settings": "Param\u00e8tres Globaux", + "monitoring.enabled": "Activ\u00e9", + "monitoring.continuous": "Continu (%)", + "monitoring.spike": "Pic (%)", + "monitoring.window": "Fen\u00eatre (min)", + "monitoring.cooldown": "Refroidissement (min)", + "monitoring.monitored_points": "Points Surveill\u00e9s", + "monitoring.col.name": "Nom", + "monitoring.col.continuous": "Continu", + "monitoring.col.spike": "Pic", + "monitoring.col.window": "Fen\u00eatre", + "monitoring.col.cooldown": "Refroidissement", + "monitoring.all_none": "Tous / Aucun", + "monitoring.reset": "R\u00e9initialiser", + "notification.heading": "Param\u00e8tres de Notification", + "notification.targets": "Cibles de Notification", + "notification.none_selected": "Aucune s\u00e9lection", + "notification.no_targets": "Aucune cible de notification trouv\u00e9e", + "notification.persistent": "Alertes Persistantes", + "notification.persistent_hint": "Cr\u00e9er des notifications persistantes HA", + "notification.event_bus": "Bus d'\u00c9v\u00e9nements", + "notification.event_bus_hint": "\u00c9mettre des \u00e9v\u00e9nements sur le bus d'\u00e9v\u00e9nements HA", + "notification.priority": "Priorit\u00e9", + "notification.priority.default": "Par d\u00e9faut", + "notification.priority.passive": "Passif", + "notification.priority.active": "Actif", + "notification.priority.time_sensitive": "Urgent", + "notification.priority.critical": "Critique", + "notification.hint.critical": "Outrepasse silencieux/NPD", + "notification.hint.time_sensitive": "Traverse le mode Concentration", + "notification.hint.passive": "Livraison silencieuse", + "notification.hint.active": "Livraison standard", + "notification.title_template": "Mod\u00e8le de Titre", + "notification.message_template": "Mod\u00e8le de Message", + "notification.placeholders": "Variables :", + "error.prefix": "Erreur :", + "error.failed_save": "\u00c9chec de la sauvegarde", + "error.failed": "\u00c9chou\u00e9", + "settings.heading": "Param\u00e8tres", + "settings.description": + "Les param\u00e8tres g\u00e9n\u00e9raux de l'int\u00e9gration (noms d'entit\u00e9s, pr\u00e9fixe de l'appareil, num\u00e9ros de circuit) sont g\u00e9r\u00e9s via le flux d'options de l'int\u00e9gration.", + "settings.open_link": "Ouvrir les Param\u00e8tres d'Int\u00e9gration SPAN Panel", + "header.default_name": "SPAN Panel", + "header.monitoring_settings": "Param\u00e8tres de surveillance du panneau", + "header.site": "Site", + "header.grid": "R\u00e9seau", + "header.upstream": "Amont", + "header.downstream": "Aval", + "header.solar": "Solaire", + "header.battery": "Batterie", + "header.toggle_units": "Basculer Watts / Amp\u00e8res", + "grid.unknown": "Inconnu", + "grid.configure": "Configurer le circuit", + "grid.on": "On", + "grid.off": "Off", + "subdevice.ev_charger": "Chargeur VE", + "subdevice.battery": "Batterie", + "subdevice.fallback": "Sous-appareil", + "subdevice.soc": "SoC", + "subdevice.soe": "SoE", + "subdevice.power": "Puissance", + "sidepanel.panel_monitoring": "Surveillance du Panneau", + "sidepanel.global_defaults": "Valeurs par d\u00e9faut globales pour tous les circuits", + "sidepanel.global_info": + "Les seuils de surveillance globaux s'appliquent \u00e0 tous les circuits sans remplacement personnalis\u00e9. Utilisez le flux d'options de l'int\u00e9gration pour modifier les param\u00e8tres globaux.", + "sidepanel.custom_info_prefix": + "Les seuils individuels de circuit peuvent \u00eatre configur\u00e9s en cliquant sur l'ic\u00f4ne d'engrenage d'une rang\u00e9e de circuit et en passant au mode", + "sidepanel.custom_info_suffix": ".", + "sidepanel.configure_global": "Configurer les Seuils Globaux", + "sidepanel.relay": "Relais", + "sidepanel.breaker": "Disjoncteur", + "sidepanel.relay_failed": "\u00c9chec du basculement du relais :", + "sidepanel.shedding_priority": "Priorit\u00e9 de D\u00e9lestage", + "sidepanel.priority_label": "Priorit\u00e9", + "sidepanel.shedding_failed": "\u00c9chec de la mise \u00e0 jour du d\u00e9lestage :", + "sidepanel.monitoring": "Surveillance", + "sidepanel.global": "Global", + "sidepanel.custom": "Personnalis\u00e9", + "sidepanel.continuous_pct": "Continu %", + "sidepanel.spike_pct": "Pic %", + "sidepanel.window_duration": "Dur\u00e9e de fen\u00eatre", + "sidepanel.cooldown": "Refroidissement", + "sidepanel.monitoring_toggle_failed": "\u00c9chec du basculement de surveillance :", + "sidepanel.clear_monitoring_failed": "\u00c9chec de l'effacement de surveillance :", + "sidepanel.save_threshold_failed": "\u00c9chec de la sauvegarde du seuil :", + "status.monitoring": "Surveillance", + "status.circuits": "circuits", + "status.mains": "alimentation", + "status.warning": "avertissement", + "status.warnings": "avertissements", + "status.alert": "alerte", + "status.alerts": "alertes", + "status.override": "remplacement", + "status.overrides": "remplacements", + "card.no_device": "Ouvrez l'\u00e9diteur de carte et s\u00e9lectionnez votre appareil SPAN Panel.", + "card.device_not_found": "Appareil de panneau introuvable. V\u00e9rifiez device_id dans la configuration de la carte.", + "card.loading": "Chargement...", + "card.topology_error": + "La r\u00e9ponse de topologie ne contient pas panel_size et aucun circuit trouv\u00e9. Mettez \u00e0 jour l'int\u00e9gration SPAN Panel.", + "card.panel_size_error": + "Impossible de d\u00e9terminer panel_size. Aucun circuit trouv\u00e9 et aucun attribut panel_size. Mettez \u00e0 jour l'int\u00e9gration SPAN Panel.", + "editor.panel_label": "SPAN Panel", + "editor.select_panel": "S\u00e9lectionnez un panneau...", + "editor.chart_window": "Fen\u00eatre de temps du graphique", + "editor.days": "jours", + "editor.hours": "heures", + "editor.minutes": "minutes", + "editor.chart_metric": "M\u00e9trique du graphique", + "editor.visible_sections": "Sections visibles", + "editor.panel_circuits": "Circuits du panneau", + "editor.battery_bess": "Batterie (BESS)", + "editor.ev_charger_evse": "Chargeur VE (EVSE)", + "metric.power": "Puissance", + "metric.current": "Courant", + "metric.soc": "\u00c9tat de Charge", + "metric.soe": "\u00c9tat d'\u00c9nergie", + "shedding.never": "Jamais", + "shedding.soc_threshold": "Seuil SoC", + "shedding.off_grid": "Hors R\u00e9seau", + "shedding.unknown": "Inconnu", + }, + + ja: { + "tab.panel": "\u30d1\u30cd\u30eb", + "tab.monitoring": "\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0", + "tab.settings": "\u8a2d\u5b9a", + "monitoring.heading": "\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0", + "monitoring.global_settings": "\u30b0\u30ed\u30fc\u30d0\u30eb\u8a2d\u5b9a", + "monitoring.enabled": "\u6709\u52b9", + "monitoring.continuous": "\u7d99\u7d9a (%)", + "monitoring.spike": "\u30b9\u30d1\u30a4\u30af (%)", + "monitoring.window": "\u30a6\u30a3\u30f3\u30c9\u30a6 (\u5206)", + "monitoring.cooldown": "\u30af\u30fc\u30eb\u30c0\u30a6\u30f3 (\u5206)", + "monitoring.monitored_points": "\u76e3\u8996\u30dd\u30a4\u30f3\u30c8", + "monitoring.col.name": "\u540d\u524d", + "monitoring.col.continuous": "\u7d99\u7d9a", + "monitoring.col.spike": "\u30b9\u30d1\u30a4\u30af", + "monitoring.col.window": "\u30a6\u30a3\u30f3\u30c9\u30a6", + "monitoring.col.cooldown": "\u30af\u30fc\u30eb\u30c0\u30a6\u30f3", + "monitoring.all_none": "\u5168\u9078\u629e / \u5168\u89e3\u9664", + "monitoring.reset": "\u30ea\u30bb\u30c3\u30c8", + "notification.heading": "\u901a\u77e5\u8a2d\u5b9a", + "notification.targets": "\u901a\u77e5\u5148", + "notification.none_selected": "\u672a\u9078\u629e", + "notification.no_targets": "\u901a\u77e5\u5148\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093", + "notification.persistent": "\u6c38\u7d9a\u30a2\u30e9\u30fc\u30c8", + "notification.persistent_hint": "HA\u6c38\u7d9a\u901a\u77e5\u3092\u4f5c\u6210", + "notification.event_bus": "\u30a4\u30d9\u30f3\u30c8\u30d0\u30b9", + "notification.event_bus_hint": "HA\u30a4\u30d9\u30f3\u30c8\u30d0\u30b9\u3067\u30a4\u30d9\u30f3\u30c8\u3092\u767a\u884c", + "notification.priority": "\u512a\u5148\u5ea6", + "notification.priority.default": "\u30c7\u30d5\u30a9\u30eb\u30c8", + "notification.priority.passive": "\u30d1\u30c3\u30b7\u30d6", + "notification.priority.active": "\u30a2\u30af\u30c6\u30a3\u30d6", + "notification.priority.time_sensitive": "\u7dca\u6025", + "notification.priority.critical": "\u91cd\u5927", + "notification.hint.critical": "\u30b5\u30a4\u30ec\u30f3\u30c8/\u304a\u3084\u3059\u307f\u30e2\u30fc\u30c9\u3092\u7121\u8996", + "notification.hint.time_sensitive": "\u96c6\u4e2d\u30e2\u30fc\u30c9\u3092\u7a81\u7834", + "notification.hint.passive": "\u30b5\u30a4\u30ec\u30f3\u30c8\u914d\u4fe1", + "notification.hint.active": "\u6a19\u6e96\u914d\u4fe1", + "notification.title_template": "\u30bf\u30a4\u30c8\u30eb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8", + "notification.message_template": "\u30e1\u30c3\u30bb\u30fc\u30b8\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8", + "notification.placeholders": "\u30d7\u30ec\u30fc\u30b9\u30db\u30eb\u30c0\u30fc:", + "error.prefix": "\u30a8\u30e9\u30fc:", + "error.failed_save": "\u4fdd\u5b58\u306b\u5931\u6557", + "error.failed": "\u5931\u6557", + "settings.heading": "\u8a2d\u5b9a", + "settings.description": + "\u7d71\u5408\u306e\u4e00\u822c\u8a2d\u5b9a\uff08\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u540d\u3001\u30c7\u30d0\u30a4\u30b9\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3001\u56de\u8def\u756a\u53f7\uff09\u306f\u7d71\u5408\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u30d5\u30ed\u30fc\u3067\u7ba1\u7406\u3055\u308c\u307e\u3059\u3002", + "settings.open_link": "SPAN Panel\u7d71\u5408\u8a2d\u5b9a\u3092\u958b\u304f", + "header.default_name": "SPAN Panel", + "header.monitoring_settings": "\u30d1\u30cd\u30eb\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0\u8a2d\u5b9a", + "header.site": "\u30b5\u30a4\u30c8", + "header.grid": "\u30b0\u30ea\u30c3\u30c9", + "header.upstream": "\u4e0a\u6d41", + "header.downstream": "\u4e0b\u6d41", + "header.solar": "\u30bd\u30fc\u30e9\u30fc", + "header.battery": "\u30d0\u30c3\u30c6\u30ea\u30fc", + "header.toggle_units": "\u30ef\u30c3\u30c8/\u30a2\u30f3\u30da\u30a2\u5207\u308a\u66ff\u3048", + "grid.unknown": "\u4e0d\u660e", + "grid.configure": "\u56de\u8def\u3092\u8a2d\u5b9a", + "grid.on": "\u30aa\u30f3", + "grid.off": "\u30aa\u30d5", + "subdevice.ev_charger": "EV\u5145\u96fb\u5668", + "subdevice.battery": "\u30d0\u30c3\u30c6\u30ea\u30fc", + "subdevice.fallback": "\u30b5\u30d6\u30c7\u30d0\u30a4\u30b9", + "subdevice.soc": "SoC", + "subdevice.soe": "SoE", + "subdevice.power": "\u96fb\u529b", + "sidepanel.panel_monitoring": "\u30d1\u30cd\u30eb\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0", + "sidepanel.global_defaults": "\u5168\u56de\u8def\u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30c7\u30d5\u30a9\u30eb\u30c8", + "sidepanel.global_info": + "\u30b0\u30ed\u30fc\u30d0\u30eb\u76e3\u8996\u30b7\u304d\u3044\u5024\u306f\u30ab\u30b9\u30bf\u30e0\u4e0a\u66f8\u304d\u306e\u306a\u3044\u5168\u56de\u8def\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002\u7d71\u5408\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u30d5\u30ed\u30fc\u3067\u30b0\u30ed\u30fc\u30d0\u30eb\u8a2d\u5b9a\u3092\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "sidepanel.custom_info_prefix": + "\u500b\u5225\u306e\u56de\u8def\u3057\u304d\u3044\u5024\u306f\u3001\u56de\u8def\u884c\u306e\u6b6f\u8eca\u30a2\u30a4\u30b3\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30e2\u30fc\u30c9\u3092\u5207\u308a\u66ff\u3048\u3066\u8a2d\u5b9a\u3067\u304d\u307e\u3059", + "sidepanel.custom_info_suffix": "\u3002", + "sidepanel.configure_global": "\u30b0\u30ed\u30fc\u30d0\u30eb\u3057\u304d\u3044\u5024\u3092\u8a2d\u5b9a", + "sidepanel.relay": "\u30ea\u30ec\u30fc", + "sidepanel.breaker": "\u30d6\u30ec\u30fc\u30ab\u30fc", + "sidepanel.relay_failed": "\u30ea\u30ec\u30fc\u5207\u308a\u66ff\u3048\u5931\u6557:", + "sidepanel.shedding_priority": "\u30b7\u30a7\u30c7\u30a3\u30f3\u30b0\u512a\u5148\u5ea6", + "sidepanel.priority_label": "\u512a\u5148\u5ea6", + "sidepanel.shedding_failed": "\u30b7\u30a7\u30c7\u30a3\u30f3\u30b0\u66f4\u65b0\u5931\u6557:", + "sidepanel.monitoring": "\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0", + "sidepanel.global": "\u30b0\u30ed\u30fc\u30d0\u30eb", + "sidepanel.custom": "\u30ab\u30b9\u30bf\u30e0", + "sidepanel.continuous_pct": "\u7d99\u7d9a %", + "sidepanel.spike_pct": "\u30b9\u30d1\u30a4\u30af %", + "sidepanel.window_duration": "\u30a6\u30a3\u30f3\u30c9\u30a6\u6642\u9593", + "sidepanel.cooldown": "\u30af\u30fc\u30eb\u30c0\u30a6\u30f3", + "sidepanel.monitoring_toggle_failed": "\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0\u5207\u308a\u66ff\u3048\u5931\u6557:", + "sidepanel.clear_monitoring_failed": "\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0\u30af\u30ea\u30a2\u5931\u6557:", + "sidepanel.save_threshold_failed": "\u3057\u304d\u3044\u5024\u4fdd\u5b58\u5931\u6557:", + "status.monitoring": "\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0", + "status.circuits": "\u56de\u8def", + "status.mains": "\u4e3b\u96fb\u6e90", + "status.warning": "\u8b66\u544a", + "status.warnings": "\u8b66\u544a", + "status.alert": "\u30a2\u30e9\u30fc\u30c8", + "status.alerts": "\u30a2\u30e9\u30fc\u30c8", + "status.override": "\u4e0a\u66f8\u304d", + "status.overrides": "\u4e0a\u66f8\u304d", + "card.no_device": + "\u30ab\u30fc\u30c9\u30a8\u30c7\u30a3\u30bf\u3092\u958b\u3044\u3066SPAN Panel\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "card.device_not_found": + "\u30d1\u30cd\u30eb\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u30ab\u30fc\u30c9\u8a2d\u5b9a\u306edevice_id\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "card.loading": "\u8aad\u307f\u8fbc\u307f\u4e2d...", + "card.topology_error": + "\u30c8\u30dd\u30ed\u30b8\u30fc\u5fdc\u7b54\u306bpanel_size\u304c\u306a\u304f\u3001\u56de\u8def\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002SPAN Panel\u7d71\u5408\u3092\u66f4\u65b0\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "card.panel_size_error": + "panel_size\u3092\u5224\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002\u56de\u8def\u304cpanel_size\u5c5e\u6027\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002SPAN Panel\u7d71\u5408\u3092\u66f4\u65b0\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "editor.panel_label": "SPAN Panel", + "editor.select_panel": "\u30d1\u30cd\u30eb\u3092\u9078\u629e...", + "editor.chart_window": "\u30b0\u30e9\u30d5\u6642\u9593\u30a6\u30a3\u30f3\u30c9\u30a6", + "editor.days": "\u65e5", + "editor.hours": "\u6642\u9593", + "editor.minutes": "\u5206", + "editor.chart_metric": "\u30b0\u30e9\u30d5\u6307\u6a19", + "editor.visible_sections": "\u8868\u793a\u30bb\u30af\u30b7\u30e7\u30f3", + "editor.panel_circuits": "\u30d1\u30cd\u30eb\u56de\u8def", + "editor.battery_bess": "\u30d0\u30c3\u30c6\u30ea\u30fc (BESS)", + "editor.ev_charger_evse": "EV\u5145\u96fb\u5668 (EVSE)", + "metric.power": "\u96fb\u529b", + "metric.current": "\u96fb\u6d41", + "metric.soc": "\u5145\u96fb\u72b6\u614b", + "metric.soe": "\u30a8\u30cd\u30eb\u30ae\u30fc\u72b6\u614b", + "shedding.never": "\u306a\u3057", + "shedding.soc_threshold": "SoC\u3057\u304d\u3044\u5024", + "shedding.off_grid": "\u30aa\u30d5\u30b0\u30ea\u30c3\u30c9", + "shedding.unknown": "\u4e0d\u660e", + }, + + pt: { + "tab.panel": "Painel", + "tab.monitoring": "Monitoramento", + "tab.settings": "Configura\u00e7\u00f5es", + "monitoring.heading": "Monitoramento", + "monitoring.global_settings": "Configura\u00e7\u00f5es Globais", + "monitoring.enabled": "Ativado", + "monitoring.continuous": "Cont\u00ednuo (%)", + "monitoring.spike": "Pico (%)", + "monitoring.window": "Janela (min)", + "monitoring.cooldown": "Resfriamento (min)", + "monitoring.monitored_points": "Pontos Monitorados", + "monitoring.col.name": "Nome", + "monitoring.col.continuous": "Cont\u00ednuo", + "monitoring.col.spike": "Pico", + "monitoring.col.window": "Janela", + "monitoring.col.cooldown": "Resfriamento", + "monitoring.all_none": "Todos / Nenhum", + "monitoring.reset": "Redefinir", + "notification.heading": "Configura\u00e7\u00f5es de Notifica\u00e7\u00e3o", + "notification.targets": "Destinos de Notifica\u00e7\u00e3o", + "notification.none_selected": "Nenhum selecionado", + "notification.no_targets": "Nenhum destino de notifica\u00e7\u00e3o encontrado", + "notification.persistent": "Alertas Persistentes", + "notification.persistent_hint": "Criar notifica\u00e7\u00f5es persistentes no HA", + "notification.event_bus": "Barramento de Eventos", + "notification.event_bus_hint": "Emitir eventos no barramento de eventos do HA", + "notification.priority": "Prioridade", + "notification.priority.default": "Padr\u00e3o", + "notification.priority.passive": "Passivo", + "notification.priority.active": "Ativo", + "notification.priority.time_sensitive": "Urgente", + "notification.priority.critical": "Cr\u00edtico", + "notification.hint.critical": "Substitui silencioso/N\u00e3o perturbar", + "notification.hint.time_sensitive": "Atravessa o modo Foco", + "notification.hint.passive": "Entrega silenciosa", + "notification.hint.active": "Entrega padr\u00e3o", + "notification.title_template": "Modelo de T\u00edtulo", + "notification.message_template": "Modelo de Mensagem", + "notification.placeholders": "Vari\u00e1veis:", + "error.prefix": "Erro:", + "error.failed_save": "Falha ao salvar", + "error.failed": "Falhou", + "settings.heading": "Configura\u00e7\u00f5es", + "settings.description": + "As configura\u00e7\u00f5es gerais da integra\u00e7\u00e3o (nomes de entidades, prefixo do dispositivo, n\u00fameros de circuito) s\u00e3o gerenciadas atrav\u00e9s do fluxo de op\u00e7\u00f5es da integra\u00e7\u00e3o.", + "settings.open_link": "Abrir Configura\u00e7\u00f5es de Integra\u00e7\u00e3o SPAN Panel", + "header.default_name": "SPAN Panel", + "header.monitoring_settings": "Configura\u00e7\u00f5es de monitoramento do painel", + "header.site": "Local", + "header.grid": "Rede", + "header.upstream": "Montante", + "header.downstream": "Jusante", + "header.solar": "Solar", + "header.battery": "Bateria", + "header.toggle_units": "Alternar Watts / Amperes", + "grid.unknown": "Desconhecido", + "grid.configure": "Configurar circuito", + "grid.on": "Lig", + "grid.off": "Des", + "subdevice.ev_charger": "Carregador VE", + "subdevice.battery": "Bateria", + "subdevice.fallback": "Sub-dispositivo", + "subdevice.soc": "SoC", + "subdevice.soe": "SoE", + "subdevice.power": "Pot\u00eancia", + "sidepanel.panel_monitoring": "Monitoramento do Painel", + "sidepanel.global_defaults": "Padr\u00f5es globais para todos os circuitos", + "sidepanel.global_info": + "Os limites de monitoramento global se aplicam a todos os circuitos sem substitui\u00e7\u00f5es personalizadas. Use o fluxo de op\u00e7\u00f5es da integra\u00e7\u00e3o para alterar as configura\u00e7\u00f5es globais.", + "sidepanel.custom_info_prefix": + "Os limites individuais de circuito podem ser configurados clicando no \u00edcone de engrenagem em uma linha de circuito e alternando para o modo", + "sidepanel.custom_info_suffix": ".", + "sidepanel.configure_global": "Configurar Limites Globais", + "sidepanel.relay": "Rel\u00e9", + "sidepanel.breaker": "Disjuntor", + "sidepanel.relay_failed": "Falha ao alternar rel\u00e9:", + "sidepanel.shedding_priority": "Prioridade de Desligamento", + "sidepanel.priority_label": "Prioridade", + "sidepanel.shedding_failed": "Falha ao atualizar desligamento:", + "sidepanel.monitoring": "Monitoramento", + "sidepanel.global": "Global", + "sidepanel.custom": "Personalizado", + "sidepanel.continuous_pct": "Cont\u00ednuo %", + "sidepanel.spike_pct": "Pico %", + "sidepanel.window_duration": "Dura\u00e7\u00e3o da janela", + "sidepanel.cooldown": "Resfriamento", + "sidepanel.monitoring_toggle_failed": "Falha ao alternar monitoramento:", + "sidepanel.clear_monitoring_failed": "Falha ao limpar monitoramento:", + "sidepanel.save_threshold_failed": "Falha ao salvar limite:", + "status.monitoring": "Monitoramento", + "status.circuits": "circuitos", + "status.mains": "alimenta\u00e7\u00e3o", + "status.warning": "aviso", + "status.warnings": "avisos", + "status.alert": "alerta", + "status.alerts": "alertas", + "status.override": "substitui\u00e7\u00e3o", + "status.overrides": "substitui\u00e7\u00f5es", + "card.no_device": "Abra o editor do cart\u00e3o e selecione seu dispositivo SPAN Panel.", + "card.device_not_found": "Dispositivo do painel n\u00e3o encontrado. Verifique device_id na configura\u00e7\u00e3o do cart\u00e3o.", + "card.loading": "Carregando...", + "card.topology_error": "A resposta de topologia n\u00e3o cont\u00e9m panel_size e nenhum circuito encontrado. Atualize a integra\u00e7\u00e3o SPAN Panel.", + "card.panel_size_error": + "N\u00e3o foi poss\u00edvel determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integra\u00e7\u00e3o SPAN Panel.", + "editor.panel_label": "SPAN Panel", + "editor.select_panel": "Selecione um painel...", + "editor.chart_window": "Janela de tempo do gr\u00e1fico", + "editor.days": "dias", + "editor.hours": "horas", + "editor.minutes": "minutos", + "editor.chart_metric": "M\u00e9trica do gr\u00e1fico", + "editor.visible_sections": "Se\u00e7\u00f5es vis\u00edveis", + "editor.panel_circuits": "Circuitos do painel", + "editor.battery_bess": "Bateria (BESS)", + "editor.ev_charger_evse": "Carregador VE (EVSE)", + "metric.power": "Pot\u00eancia", + "metric.current": "Corrente", + "metric.soc": "Estado de Carga", + "metric.soe": "Estado de Energia", + "shedding.never": "Nunca", + "shedding.soc_threshold": "Limite SoC", + "shedding.off_grid": "Fora da Rede", + "shedding.unknown": "Desconhecido", + }, +}; + +/** + * Set the active language. Call once per render cycle with hass.language. + * Falls back to "en" for unsupported languages. + */ +export function setLanguage(lang) { + _lang = translations[lang] ? lang : "en"; +} + +/** + * Look up a translation key. Returns the English fallback when the key + * is missing in the active language. + */ +export function t(key) { + return translations[_lang]?.[key] ?? translations.en[key] ?? key; +} diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 9550bdd..58ec507 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -1,4 +1,5 @@ import { INTEGRATION_DOMAIN } from "../constants.js"; +import { setLanguage, t } from "../i18n.js"; import "../core/side-panel.js"; import { DashboardTab } from "./tab-dashboard.js"; import { MonitoringTab } from "./tab-monitoring.js"; @@ -104,6 +105,7 @@ export class SpanPanelElement extends HTMLElement { } _render() { + setLanguage(this._hass?.language); const multiPanel = this._panels.length > 1; const selectedPanel = this._panels.find(p => p.id === this._selectedPanelId); const panelLabel = selectedPanel ? selectedPanel.name_by_user || selectedPanel.name || selectedPanel.id : ""; @@ -132,9 +134,9 @@ export class SpanPanelElement extends HTMLElement {
- - - + + +
diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 1005e1b..28da305 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -1,5 +1,6 @@ import { INTEGRATION_DOMAIN } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; +import { t } from "../i18n.js"; const FIELD_STYLE = ` display:flex;align-items:center;gap:8px;margin-bottom:8px; @@ -101,7 +102,7 @@ export class MonitoringTab { const allNotifyTargets = [...targetSet].sort(); const rawTargets = globalSettings.notify_targets || "notify.notify"; - const selectedTargets = (typeof rawTargets === "string" ? rawTargets.split(",") : rawTargets).map(t => t.trim()).filter(Boolean); + const selectedTargets = (typeof rawTargets === "string" ? rawTargets.split(",") : rawTargets).map(s => s.trim()).filter(Boolean); const titleTemplate = globalSettings.notification_title_template || "SPAN: {name} {alert_type}"; const messageTemplate = globalSettings.notification_message_template || "{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)"; const persistentNotifications = globalSettings.enable_persistent_notifications !== false; @@ -140,7 +141,7 @@ export class MonitoringTab { hasOverride ? `` : "" } @@ -176,7 +177,7 @@ export class MonitoringTab { hasOverride ? `` : "" } @@ -188,49 +189,49 @@ export class MonitoringTab { container.innerHTML = `
-

Monitoring

+

${t("monitoring.heading")}

-

Global Settings

+

${t("monitoring.global_settings")}

- Continuous (%) + ${t("monitoring.continuous")}
- Spike (%) + ${t("monitoring.spike")}
- Window (min) + ${t("monitoring.window")}
- Cooldown (min) + ${t("monitoring.cooldown")}

-

Notification Settings

+

${t("notification.heading")}

- Notify Targets + ${t("notification.targets")}
${ allNotifyTargets.length === 0 - ? `
No notify targets found
` + ? `
${t("notification.no_targets")}
` : allNotifyTargets .map(target => { const checked = selectedTargets.includes(target); @@ -271,51 +272,51 @@ export class MonitoringTab {
- Persistent Alerts + ${t("notification.persistent")}
- Event Bus + ${t("notification.event_bus")}
- Priority + ${t("notification.priority")} ${ currentPriority === "critical" - ? "Overrides silent/DND" + ? t("notification.hint.critical") : currentPriority === "time-sensitive" - ? "Breaks through Focus" + ? t("notification.hint.time_sensitive") : currentPriority === "passive" - ? "Delivers silently" + ? t("notification.hint.passive") : currentPriority === "active" - ? "Standard delivery" + ? t("notification.hint.active") : "" }
- Title Template + ${t("notification.title_template")}
- Message Template + ${t("notification.message_template")}
- Placeholders: {name} {entity_id} {alert_type} + ${t("notification.placeholders")} {name} {entity_id} {alert_type} {current_a} {breaker_rating_a} {threshold_pct} {utilization_pct} {window_m}
@@ -340,15 +341,15 @@ export class MonitoringTab {
-

Monitored Points

+

${t("monitoring.monitored_points")}

- - - - - + + + + + @@ -359,7 +360,7 @@ export class MonitoringTab { - All / None + ${t("monitoring.all_none")} @@ -418,7 +419,7 @@ export class MonitoringTab { await this._callSetGlobal(hass, data); await this.render(container, hass); } catch (err) { - statusEl.textContent = `Error: ${err.message || "Failed to save"}`; + statusEl.textContent = `${t("error.prefix")} ${err.message || t("error.failed_save")}`; statusEl.style.color = "var(--error-color, #f44336)"; } }, 500); @@ -444,7 +445,7 @@ export class MonitoringTab { } } catch (err) { if (statusEl2) { - statusEl2.textContent = `Error: ${err.message || "Failed"}`; + statusEl2.textContent = `${t("error.prefix")} ${err.message || t("error.failed")}`; statusEl2.style.color = "var(--error-color, #f44336)"; } return; @@ -486,7 +487,7 @@ export class MonitoringTab { cb.addEventListener("change", () => { const checked = [...container.querySelectorAll(".notify-target-cb:checked")]; const targets = checked.map(c => c.value); - label.textContent = targets.length ? targets.join(", ") : "None selected"; + label.textContent = targets.length ? targets.join(", ") : t("notification.none_selected"); clearTimeout(this._debounceTimer); this._debounceTimer = setTimeout(async () => { diff --git a/src/panel/tab-settings.js b/src/panel/tab-settings.js index fd0cc61..3cb0cef 100644 --- a/src/panel/tab-settings.js +++ b/src/panel/tab-settings.js @@ -1,17 +1,18 @@ +import { t } from "../i18n.js"; + export class SettingsTab { render(container, configEntryId) { const href = configEntryId ? `/config/integrations/integration/span_panel#config_entry=${configEntryId}` : "/config/integrations/integration/span_panel"; container.innerHTML = `
-

Settings

+

${t("settings.heading")}

- General integration settings (entity naming, device prefix, - circuit numbers) are managed through the integration's options flow. + ${t("settings.description")}

- Open SPAN Panel Integration Settings → + ${t("settings.open_link")} →
`; From 04a7545673ae3322a95f6379d3135106656579e8 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:58:39 -0700 Subject: [PATCH 033/101] chore: bump version to 0.9.0 and update changelog --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3e98b..7364575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.9.0 + +- Add integration panel with tab router and multi-panel selector +- Add monitoring tab with overrides table, summary bar, and notification settings +- Add settings tab with integration link and global monitoring configuration +- Add side panel for circuit and panel configuration +- Add A/W toggle switching all values and chart axes +- Add shedding icons, monitoring indicators, and gear icons to circuit cells +- Add i18n support with translations for en, es, fr, ja, pt +- Use topology panel_entities instead of pattern matching + ## 0.8.9 - Show amps instead of watts above circuit graphs when chart metric is set to `current` diff --git a/package.json b/package.json index 445226b..7f9336e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "span-panel-card", - "version": "0.8.8", + "version": "0.9.0", "private": true, "type": "module", "scripts": { From f1942f62887e815d72b778df658b5f335b5408c2 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:03:13 -0700 Subject: [PATCH 034/101] fix: clear chart history and reload on W/A metric toggle When switching between watts and amps, chart history is now fully cleared and re-fetched from the recorder for the correct entity. Charts await the history load before rendering, preventing stale data from the previous metric appearing as a false spike or drop. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/span-panel-card.js | 6 +++++- src/panel/tab-dashboard.js | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 7d395ca..b585963 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="span_panel",o="CLOSED",a="pv",s="bess",r="evse",c="sub_",l={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},d={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:l.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},p="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function g(e){return String(e).replace(/[&<>"']/g,e=>h[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function _(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function v(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function b(e){return l[e.chart_metric]||l[n]}function y(e,t){const n=function(e){return b(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}const w=l.power;function x(e){return w.unit(e)}function C(e){return(e<0?"-":"")+w.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function E(e){return e%2==0?1:0}function $(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":E(t)===E(n)?"col-span":"row-span"}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:i,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function N(e,n,i,s,r,c,l,d,h,m){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,y=n.device_type===a||v<0,w=n.entities?.switch,S=w?l.states[w]:null,k=S?"on"===S.state:(_?.attributes?.relay_state||n.relay_state)===o,E=n.breaker_rating_a,$=E?`${Math.round(E)}A`:"",P=g(n.name||t("grid.unknown")),N=b(d);let A;if("current"===N.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;A=`${N.format(i)}A`}else A=`${C(v)}${x(v)}`;const M=u[m||"unknown"]||u.unknown,z=``,T=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),L=T?p:"#555",R=``;let D="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);D=`${Math.round(e)}%`}const F=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${P}\n
\n
\n \n ${A}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(k?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${z}\n ${D}\n ${R}\n
\n
\n
\n `}function A(e,t){return`\n
\n \n
\n `}const M={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function R(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return R(e,M)}function F(e){return R(e,z)}function j(e){return R(e,T)}function I(e){return R(e,L)}function H(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${g(c)}:\n ${g(d)}\n
\n `}return a}function O(e,n,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:t("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:t("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${g(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function W(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,v(t,r,c))}}}function q(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===s&&(e.soc=F(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}async function V(e,t,n,i){if(!t||!e)return;const o=m(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=y(i,n);t&&(a.push(t),s.set(t,e))}if(function(e,t,n){for(const{entityId:i,key:o}of q(e))t.push(i),n.set(i,o)}(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await W(e,a,s,o,i)}function B(e,t,i,o,a,s,r,c){const{options:d,series:u}=function(e,t,i,o,a){i||(i=l[n]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,c=Date.now(),d=c-t,u=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return u&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:h}}(i,o,a,s,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=d,p.data=u}function G(e,n,i,s,r){if(!e||!i||!n)return;const c=m(s);let l=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==a&&(l+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=S(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),u=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),u&&(u.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=S(e)}else p.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,s,l);const d=b(s),p="current"===d.entityRole;for(const[s,l]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${s}"]`);if(!i)continue;const h=l.entities?.power,g=h?n.states[h]:null,m=g&&parseFloat(g.state)||0,f=l.device_type===a||m<0,_=l.entities?.switch,v=_?n.states[_]:null,b=v?"on"===v.state:(g?.attributes?.relay_state||l.relay_state)===o,y=i.querySelector(".power-value");if(y)if(p){const e=l.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;y.innerHTML=`${d.format(i)}A`}else y.innerHTML=`${C(m)}${x(m)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(b?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=t(b?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!b),i.classList.toggle("circuit-producer",f);const S=l.entities?.select,k=S?n.states[S]:null,E=k?k.state:"unknown",$=u[E]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",$.icon),P.style.color=$.color,P.title=$.label());const N=i.querySelector(".chart-container");if(N){B(N,n,r.get(s)||[],c,d,f,i.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const J=Object.keys(u).filter(e=>"unknown"!==e);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=t("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${g(String(t.breaker_rating_a))}A · ${g(String(t.voltage))}V · Tabs [${g(String(t.tabs))}]`,i=this._createHeader(g(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of J){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,u=document.createElement("div");u.className="radio-group",u.innerHTML=`\n \n \n `,l.appendChild(u);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",m,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",n)),l.appendChild(p),s.addEventListener("change",()=>{const e=s.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=u.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,n,i,o,a,s,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const u=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(a),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const h=document.createElement("span");return h.textContent=s,u.appendChild(p),u.appendChild(h),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(u),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(i,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",K);class X extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new P}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const o=await e.callWS({type:`${i}/panel_topology`,device_id:n}),a=o.panel_size||U(o.circuits);if(!a)throw new Error(t("card.topology_error"));return{topology:o,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:a}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[o,a]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=o.find(e=>e.id===n)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=a.filter(e=>e.device_id===n),c=o.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=a.filter(e=>l.has(e.device_id)),u={},p=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!a.every(Number.isFinite))continue;const s=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(s[e])){r=s[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");u[r]={tabs:a,name:c,voltage:n.attributes.voltage||(2===a.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(s.identifiers)for(const e of s.identifiers)e[0]===i&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=U(u)),!g)throw new Error(t("card.panel_size_error"));const m={};for(const t of c){const n=a.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),o=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),s={};for(const t of n)s[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};m[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":o?"evse":"unknown",entities:s}}return{topology:{serial:h,firmware:s.sw_version||"",panel_size:g,device_id:n,device_name:s.name_by_user||s.name||t("header.default_name"),circuits:u,sub_devices:m},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await V(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now(),t=e-this._durationMs,n=f(this._durationMs);for(const[i,o]of Object.entries(this._topology.circuits)){const a=y(o,this._config);if(!a)continue;const s=this._hass.states[a],r=s&&parseFloat(s.state)||0;_(this._powerHistory,i,r,e,t,n)}for(const{entityId:i,key:o}of q(this._topology)){const a=this._hass.states[i],s=a&&parseFloat(a.state)||0;_(this._powerHistory,o,s,e,t,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){G(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(e,t,n,i,o){if(!n.sub_devices)return;const a=m(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${C(i)} ${x(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let s=d.power;n.endsWith("_soc")?s=d.soc:n.endsWith("_soe")&&(s=d.soe);const r=!!e.closest(".bess-chart-col");B(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}_onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._rendered=!1,this._render())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const a=o.entities?.switch;if(!a)return;const s=this._hass.states[a];if(!s)return void console.warn("SPAN Panel: switch entity not found:",a);const r="on"===s.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}_onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=t.dataset.uuid;if(!i||!this._topology)return;const o=this._topology.circuits[i];if(!o)return;const a=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;n.open({...o,uuid:i,monitoringInfo:a})}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${g(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=g(e.device_name||t("header.default_name")),o=g(e.serial||""),a=g(e.firmware||""),s="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,u=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(n,this._config)),a=this._monitoringCache.status,c=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${a>0?`${a} ${t(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${t(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(a),l=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":$(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===E(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let u="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),p=s.get(n);if(u+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);u+=N(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),u+=`
${n}
`;continue}if(!c.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(u+=A(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);u+=N(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(u+=A(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);u+=N(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}u+=`
${n}
`}return u}(n,i,0,e,this._config,a),d=function(e,n,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let c="";if(!e.sub_devices)return c;for(const[l,d]of Object.entries(e.sub_devices)){if(d.type===s&&!o)continue;if(d.type===r&&!a)continue;const e=d.type===r?t("subdevice.ev_charger"):d.type===s?t("subdevice.battery"):t("subdevice.fallback"),u=D(d),p=u?n.states[u]:null,h=p&&parseFloat(p.state)||0,m=d.type===s,f=m?F(d):null,_=m?j(d):null,v=m?I(d):null,b=H(d,n,i,new Set([u,f,_,v].filter(Boolean))),y=O(l,0,m,u,f,_);c+=`\n
\n
\n ${g(e)}\n ${g(d.name||"")}\n ${u?`${C(h)} ${x(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return c}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${c}\n ${!1!==this._config.show_panel?`\n
\n ${l}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class Q extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===i)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===i)?.[1]||"",o=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${o} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.panel_label"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_window"),s.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const a=document.createElement("input");a.type="number",a.min=t,a.max=n,a.value=String(e),a.style.cssText=c;const s=document.createElement("span");return s.textContent=i,s.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(a),o.appendChild(s),{wrap:o,input:a}},d=parseInt(this._config.history_days)||0,u=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(u,"0","23",t("editor.hours")),m=l(p,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(m.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),m.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(m.wrap),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=m.input}_buildMetricSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_metric"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const a=document.createElement("label");a.textContent=t("editor.visible_sections"),a.style.cssText=n,o.appendChild(a);const s=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of s){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let a=null;e.subDeviceType&&(a=document.createElement("div"),a.style.cssText="padding-left: 26px;",a.style.display=n.checked?"block":"none",o.appendChild(a),this._entityContainers[e.subDeviceType]=a),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},a&&(a.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const a=document.createElement("div");a.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const s=document.createElement("input");s.type="checkbox",s.checked=!0===t[i],s.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",a.appendChild(s),a.appendChild(r),e.appendChild(a),s.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};s.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${i}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(l)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",X),customElements.define("span-panel-card-editor",Q),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="span_panel",o="CLOSED",a="pv",s="bess",r="evse",c="sub_",l={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},d={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:l.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},p="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function g(e){return String(e).replace(/[&<>"']/g,e=>h[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function _(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function v(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function b(e){return l[e.chart_metric]||l[n]}function y(e,t){const n=function(e){return b(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}const w=l.power;function x(e){return w.unit(e)}function C(e){return(e<0?"-":"")+w.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function E(e){return e%2==0?1:0}function $(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":E(t)===E(n)?"col-span":"row-span"}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:i,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function N(e,n,i,s,r,c,l,d,h,m){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,y=n.device_type===a||v<0,w=n.entities?.switch,S=w?l.states[w]:null,k=S?"on"===S.state:(_?.attributes?.relay_state||n.relay_state)===o,E=n.breaker_rating_a,$=E?`${Math.round(E)}A`:"",P=g(n.name||t("grid.unknown")),N=b(d);let M;if("current"===N.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${N.format(i)}A`}else M=`${C(v)}${x(v)}`;const A=u[m||"unknown"]||u.unknown,z=``,T=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),L=T?p:"#555",R=``;let D="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);D=`${Math.round(e)}%`}const F=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${P}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(k?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${z}\n ${D}\n ${R}\n
\n
\n
\n `}function M(e,t){return`\n
\n \n
\n `}const A={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function R(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return R(e,A)}function F(e){return R(e,z)}function j(e){return R(e,T)}function H(e){return R(e,L)}function I(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${g(c)}:\n ${g(d)}\n
\n `}return a}function O(e,n,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:t("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:t("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${g(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function W(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,v(t,r,c))}}}function q(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===s&&(e.soc=F(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}async function V(e,t,n,i){if(!t||!e)return;const o=m(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=y(i,n);t&&(a.push(t),s.set(t,e))}if(function(e,t,n){for(const{entityId:i,key:o}of q(e))t.push(i),n.set(i,o)}(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await W(e,a,s,o,i)}function B(e,t,i,o,a,s,r,c){const{options:d,series:u}=function(e,t,i,o,a){i||(i=l[n]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,c=Date.now(),d=c-t,u=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return u&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:h}}(i,o,a,s,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=d,p.data=u}function G(e,n,i,s,r){if(!e||!i||!n)return;const c=m(s);let l=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==a&&(l+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=S(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),u=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),u&&(u.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=S(e)}else p.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,s,l);const d=b(s),p="current"===d.entityRole;for(const[s,l]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${s}"]`);if(!i)continue;const h=l.entities?.power,g=h?n.states[h]:null,m=g&&parseFloat(g.state)||0,f=l.device_type===a||m<0,_=l.entities?.switch,v=_?n.states[_]:null,b=v?"on"===v.state:(g?.attributes?.relay_state||l.relay_state)===o,y=i.querySelector(".power-value");if(y)if(p){const e=l.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;y.innerHTML=`${d.format(i)}A`}else y.innerHTML=`${C(m)}${x(m)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(b?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=t(b?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!b),i.classList.toggle("circuit-producer",f);const S=l.entities?.select,k=S?n.states[S]:null,E=k?k.state:"unknown",$=u[E]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",$.icon),P.style.color=$.color,P.title=$.label());const N=i.querySelector(".chart-container");if(N){B(N,n,r.get(s)||[],c,d,f,i.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const J=Object.keys(u).filter(e=>"unknown"!==e);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=t("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${g(String(t.breaker_rating_a))}A · ${g(String(t.voltage))}V · Tabs [${g(String(t.tabs))}]`,i=this._createHeader(g(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of J){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,u=document.createElement("div");u.className="radio-group",u.innerHTML=`\n \n \n `,l.appendChild(u);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",m,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",n)),l.appendChild(p),s.addEventListener("change",()=>{const e=s.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=u.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,n,i,o,a,s,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const u=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(a),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const h=document.createElement("span");return h.textContent=s,u.appendChild(p),u.appendChild(h),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(u),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(i,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",K);class X extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new P}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const o=await e.callWS({type:`${i}/panel_topology`,device_id:n}),a=o.panel_size||U(o.circuits);if(!a)throw new Error(t("card.topology_error"));return{topology:o,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:a}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[o,a]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=o.find(e=>e.id===n)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=a.filter(e=>e.device_id===n),c=o.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=a.filter(e=>l.has(e.device_id)),u={},p=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!a.every(Number.isFinite))continue;const s=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(s[e])){r=s[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");u[r]={tabs:a,name:c,voltage:n.attributes.voltage||(2===a.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(s.identifiers)for(const e of s.identifiers)e[0]===i&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=U(u)),!g)throw new Error(t("card.panel_size_error"));const m={};for(const t of c){const n=a.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),o=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),s={};for(const t of n)s[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};m[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":o?"evse":"unknown",entities:s}}return{topology:{serial:h,firmware:s.sw_version||"",panel_size:g,device_id:n,device_name:s.name_by_user||s.name||t("header.default_name"),circuits:u,sub_devices:m},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await V(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now(),t=e-this._durationMs,n=f(this._durationMs);for(const[i,o]of Object.entries(this._topology.circuits)){const a=y(o,this._config);if(!a)continue;const s=this._hass.states[a],r=s&&parseFloat(s.state)||0;_(this._powerHistory,i,r,e,t,n)}for(const{entityId:i,key:o}of q(this._topology)){const a=this._hass.states[i],s=a&&parseFloat(a.state)||0;_(this._powerHistory,o,s,e,t,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){G(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(e,t,n,i,o){if(!n.sub_devices)return;const a=m(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${C(i)} ${x(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let s=d.power;n.endsWith("_soc")?s=d.soc:n.endsWith("_soe")&&(s=d.soe);const r=!!e.closest(".bess-chart-col");B(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const a=o.entities?.switch;if(!a)return;const s=this._hass.states[a];if(!s)return void console.warn("SPAN Panel: switch entity not found:",a);const r="on"===s.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}_onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=t.dataset.uuid;if(!i||!this._topology)return;const o=this._topology.circuits[i];if(!o)return;const a=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;n.open({...o,uuid:i,monitoringInfo:a})}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${g(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=g(e.device_name||t("header.default_name")),o=g(e.serial||""),a=g(e.firmware||""),s="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,u=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(n,this._config)),a=this._monitoringCache.status,c=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${a>0?`${a} ${t(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${t(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(a),l=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":$(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===E(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let u="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),p=s.get(n);if(u+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);u+=N(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),u+=`
${n}
`;continue}if(!c.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(u+=M(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);u+=N(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(u+=M(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);u+=N(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}u+=`
${n}
`}return u}(n,i,0,e,this._config,a),d=function(e,n,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let c="";if(!e.sub_devices)return c;for(const[l,d]of Object.entries(e.sub_devices)){if(d.type===s&&!o)continue;if(d.type===r&&!a)continue;const e=d.type===r?t("subdevice.ev_charger"):d.type===s?t("subdevice.battery"):t("subdevice.fallback"),u=D(d),p=u?n.states[u]:null,h=p&&parseFloat(p.state)||0,m=d.type===s,f=m?F(d):null,_=m?j(d):null,v=m?H(d):null,b=I(d,n,i,new Set([u,f,_,v].filter(Boolean))),y=O(l,0,m,u,f,_);c+=`\n
\n
\n ${g(e)}\n ${g(d.name||"")}\n ${u?`${C(h)} ${x(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return c}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${c}\n ${!1!==this._config.show_panel?`\n
\n ${l}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class Q extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===i)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===i)?.[1]||"",o=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${o} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.panel_label"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_window"),s.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const a=document.createElement("input");a.type="number",a.min=t,a.max=n,a.value=String(e),a.style.cssText=c;const s=document.createElement("span");return s.textContent=i,s.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(a),o.appendChild(s),{wrap:o,input:a}},d=parseInt(this._config.history_days)||0,u=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(u,"0","23",t("editor.hours")),m=l(p,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(m.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),m.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(m.wrap),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=m.input}_buildMetricSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_metric"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const a=document.createElement("label");a.textContent=t("editor.visible_sections"),a.style.cssText=n,o.appendChild(a);const s=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of s){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let a=null;e.subDeviceType&&(a=document.createElement("div"),a.style.cssText="padding-left: 26px;",a.style.display=n.checked?"block":"none",o.appendChild(a),this._entityContainers[e.subDeviceType]=a),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},a&&(a.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const a=document.createElement("div");a.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const s=document.createElement("input");s.type="checkbox",s.checked=!0===t[i],s.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",a.appendChild(s),a.appendChild(r),e.appendChild(a),s.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};s.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${i}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(l)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",X),customElements.define("span-panel-card-editor",Q),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index a8fc8fe..1b3b51e 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="span_panel",a="CLOSED",s="pv",r="bess",l="evse",c="sub_",d={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:d.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},g="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>h[e])}const f=Object.keys(u).filter(e=>"unknown"!==e);class _ extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of f){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const g=r?.continuous_threshold_pct??80,h=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",g,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",h,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=s,p.appendChild(u),p.appendChild(g),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(o,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function b(e,t){const i=await e.callWS({type:`${o}/panel_topology`,device_id:t}),a=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!a)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:a}}customElements.define("span-side-panel",_);const v=d.power;function y(e){return v.unit(e)}function x(e){return(e<0?"-":"")+v.format(e)}function w(e){return(Math.abs(e)/1e3).toFixed(1)}function $(e){return Math.ceil(e/2)}function S(e){return e%2==0?1:0}function k(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return $(t)===$(n)?"row-span":S(t)===S(n)?"col-span":"row-span"}function C(e){return d[e.chart_metric]||d[i]}function E(e,t){const n=function(e){return C(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,r,l,c,d,p,h){const f=t.entities?.power,_=f?c.states[f]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===s||b<0,w=t.entities?.switch,$=w?c.states[w]:null,S=$?"on"===$.state:(_?.attributes?.relay_state||t.relay_state)===a,k=t.breaker_rating_a,E=k?`${Math.round(k)}A`:"",P=m(t.name||n("grid.unknown")),A=C(d);let z;if("current"===A.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;z=`${A.format(i)}A`}else z=`${x(b)}${y(b)}`;const N=u[h||"unknown"]||u.unknown,M=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",q=``;let j="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);j=`${Math.round(e)}%`}const I=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${E?`${E}`:""}\n ${P}\n
\n
\n \n ${z}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${M}\n ${j}\n ${q}\n
\n
\n
\n `}function z(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},M={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return q(e,N)}function I(e){return q(e,M)}function F(e){return q(e,T)}function R(e){return q(e,L)}function D(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${m(l)}:\n ${m(d)}\n
\n `}return a}function H(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function B(e){return Math.max(500,Math.floor(e/5e3))}function G(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function V(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function U(e,t,n,o,a,s,r,l){const{options:c,series:p}=function(e,t,n,o,a){n||(n=d[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,p=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),g=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],h={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return p&&(h.min=n.fixedMin,h.max=n.fixedMax),a&&"current"===n.entityRole&&(h.min=0,h.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:h,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=p}function J(e,t,i,o,r){if(!e||!i||!t)return;const l=O(o);let c=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==s&&(c+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=w(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=w(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),g=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",g&&(g.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=w(e)}else u.textContent="--";g&&(g.textContent="kW")}}const h=e.querySelector(".stat-battery .stat-value");if(h){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(h.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,c);const d=C(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=c.entities?.power,h=g?t.states[g]:null,m=h&&parseFloat(h.state)||0,f=c.device_type===s||m<0,_=c.entities?.switch,b=_?t.states[_]:null,v=b?"on"===b.state:(h?.attributes?.relay_state||c.relay_state)===a,w=i.querySelector(".power-value");if(w)if(p){const e=c.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${d.format(i)}A`}else w.innerHTML=`${x(m)}${y(m)}`;const $=i.querySelector(".toggle-pill");if($){$.className="toggle-pill "+(v?"toggle-on":"toggle-off");const e=$.querySelector(".toggle-label");e&&(e.textContent=n(v?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!v),i.classList.toggle("circuit-producer",f);const S=c.entities?.select,k=S?t.states[S]:null,C=k?k.state:"unknown",E=u[C]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const A=i.querySelector(".chart-container");if(A){U(A,t,r.get(o)||[],l,d,f,i.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function X(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${x(i)} ${y(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=p.power;n.endsWith("_soc")?s=p.soc:n.endsWith("_soe")&&(s=p.soe);const r=!!e.closest(".bess-chart-col");U(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function K(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===r&&(e.soc=I(i),e.soe=F(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function Q(e,t,n,i){if(!t||!e)return;const o=O(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=E(i,n);t&&(a.push(t),s.set(t,e))}if(K(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),l=B(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,V(t,r,l))}}}(e,a,s,o,i)}class Y{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new P,this._updateInterval=null,this._hass=null,this._config=null}async render(e,t,i,o){this.stop(),this._hass=t,this._config=o;try{const e=await b(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t);const a=this._topology,s=Math.ceil(this._panelSize/2),c=(O(o),this._monitoringCache.status),d=function(e,t){const i=m(e.device_name||n("header.default_name")),o=m(e.serial||""),a=m(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(a,o),p=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),u=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":k(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=$(Math.max(...n));0===S(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=z(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=z(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(a,s,0,t,o,c),g=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[c,d]of Object.entries(e.sub_devices)){if(d.type===r&&!o)continue;if(d.type===l&&!a)continue;const e=d.type===l?n("subdevice.ev_charger"):d.type===r?n("subdevice.battery"):n("subdevice.fallback"),p=j(d),u=p?t.states[p]:null,g=u&&parseFloat(u.state)||0,h=d.type===r,f=h?I(d):null,_=h?F(d):null,b=h?R(d):null,v=D(d,t,i,new Set([p,f,_,b].filter(Boolean))),w=H(c,0,h,p,f,_);s+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${x(g)} ${y(g)}`:""}\n
\n ${w}\n ${v}\n
\n `}return s}(a,t,o);e.innerHTML=`\n \n ${d}\n ${p}\n ${!1!==o.show_panel?`\n
\n ${u}\n
\n `:""}\n ${g?`
${g}
`:""}\n \n `,this._bindGearClicks(e,a),this._bindToggleClicks(e,a),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await Q(t,a,o,this._powerHistory)}catch{}J(e,t,a,o,this._powerHistory),X(e,t,a,o,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),J(e,this._hass,a,this._config,this._powerHistory),X(e,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const e=O(this._config),t=W(e),n=B(e),i=Date.now(),o=i-e;for(const[e,a]of Object.entries(this._topology.circuits)){const s=E(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(e)||[];c.length>0&&i-c[c.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const Z="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",ee="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",te="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",ne="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ie="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function oe(e,t,n,i,o){return`
`}class ae{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let a;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:e,return_response:!0});a=n?.response||null}catch{a=null}const s=a?.global_settings||{},r=!0===a?.enabled,l=a?.circuits||{},c=a?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=s.notify_targets||"notify.notify",g=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),h=s.notification_title_template||"SPAN: {name} {alert_type}",f=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n \n `}).join(""),C=Object.entries(c).map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=g.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${m(o)} (${m(e)})`:m(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n
NameContinuousSpikeWindowCooldown${t("monitoring.col.name")}${t("monitoring.col.continuous")}${t("monitoring.col.spike")}${t("monitoring.col.window")}${t("monitoring.col.cooldown")}
\n ${i}\n
\n \n \n ${a?``:""}\n
\n \n \n ${a?``:""}\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${C}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const E=e.querySelector("#toggle-all-circuits");E&&!$&&S&&(E.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:o,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const a=e.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:a})})])}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:a})})}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const a=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(a)),n.set(a,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const a=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:o,service:l,service_data:this._serviceData({[c]:a,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,a=n.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:i}:{circuit_id:i});await t.callService(o,s,r),await this.render(e,t)})}}class se{render(e,t){const i=t?`/config/integrations/integration/span_panel#config_entry=${t}`:"/config/integrations/integration/span_panel";e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n
\n `}}class re extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new Y,this._monitoringTab=new ae,this._settingsTab=new se}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===o)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;this._settingsTab.render(e,n);break}}}}customElements.define("span-panel",re),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="span_panel",a="CLOSED",s="pv",r="bess",l="evse",c="sub_",d={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:d.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},g="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>h[e])}const f=Object.keys(u).filter(e=>"unknown"!==e);class _ extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of f){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const g=r?.continuous_threshold_pct??80,h=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",g,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",h,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=s,p.appendChild(u),p.appendChild(g),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(o,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function b(e,t){const i=await e.callWS({type:`${o}/panel_topology`,device_id:t}),a=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!a)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:a}}customElements.define("span-side-panel",_);const v=d.power;function y(e){return v.unit(e)}function x(e){return(e<0?"-":"")+v.format(e)}function w(e){return(Math.abs(e)/1e3).toFixed(1)}function $(e){return Math.ceil(e/2)}function S(e){return e%2==0?1:0}function k(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return $(t)===$(n)?"row-span":S(t)===S(n)?"col-span":"row-span"}function C(e){return d[e.chart_metric]||d[i]}function E(e,t){const n=function(e){return C(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,r,l,c,d,p,h){const f=t.entities?.power,_=f?c.states[f]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===s||b<0,w=t.entities?.switch,$=w?c.states[w]:null,S=$?"on"===$.state:(_?.attributes?.relay_state||t.relay_state)===a,k=t.breaker_rating_a,E=k?`${Math.round(k)}A`:"",P=m(t.name||n("grid.unknown")),A=C(d);let z;if("current"===A.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;z=`${A.format(i)}A`}else z=`${x(b)}${y(b)}`;const N=u[h||"unknown"]||u.unknown,M=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",q=``;let j="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);j=`${Math.round(e)}%`}const I=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${E?`${E}`:""}\n ${P}\n
\n
\n \n ${z}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${M}\n ${j}\n ${q}\n
\n
\n
\n `}function z(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},M={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return q(e,N)}function I(e){return q(e,M)}function F(e){return q(e,T)}function R(e){return q(e,L)}function D(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${m(l)}:\n ${m(d)}\n
\n `}return a}function H(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function B(e){return Math.max(500,Math.floor(e/5e3))}function G(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function V(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function U(e,t,n,o,a,s,r,l){const{options:c,series:p}=function(e,t,n,o,a){n||(n=d[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,p=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),g=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],h={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return p&&(h.min=n.fixedMin,h.max=n.fixedMax),a&&"current"===n.entityRole&&(h.min=0,h.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:h,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=p}function J(e,t,i,o,r){if(!e||!i||!t)return;const l=O(o);let c=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==s&&(c+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=w(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=w(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),g=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",g&&(g.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=w(e)}else u.textContent="--";g&&(g.textContent="kW")}}const h=e.querySelector(".stat-battery .stat-value");if(h){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(h.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,c);const d=C(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=c.entities?.power,h=g?t.states[g]:null,m=h&&parseFloat(h.state)||0,f=c.device_type===s||m<0,_=c.entities?.switch,b=_?t.states[_]:null,v=b?"on"===b.state:(h?.attributes?.relay_state||c.relay_state)===a,w=i.querySelector(".power-value");if(w)if(p){const e=c.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${d.format(i)}A`}else w.innerHTML=`${x(m)}${y(m)}`;const $=i.querySelector(".toggle-pill");if($){$.className="toggle-pill "+(v?"toggle-on":"toggle-off");const e=$.querySelector(".toggle-label");e&&(e.textContent=n(v?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!v),i.classList.toggle("circuit-producer",f);const S=c.entities?.select,k=S?t.states[S]:null,C=k?k.state:"unknown",E=u[C]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const A=i.querySelector(".chart-container");if(A){U(A,t,r.get(o)||[],l,d,f,i.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function X(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${x(i)} ${y(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=p.power;n.endsWith("_soc")?s=p.soc:n.endsWith("_soe")&&(s=p.soe);const r=!!e.closest(".bess-chart-col");U(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function K(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===r&&(e.soc=I(i),e.soe=F(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function Q(e,t,n,i){if(!t||!e)return;const o=O(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=E(i,n);t&&(a.push(t),s.set(t,e))}if(K(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),l=B(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,V(t,r,l))}}}(e,a,s,o,i)}class Y{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new P,this._updateInterval=null,this._hass=null,this._config=null}async render(e,t,i,o){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=o;try{const e=await b(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t);const a=this._topology,s=Math.ceil(this._panelSize/2),c=(O(o),this._monitoringCache.status),d=function(e,t){const i=m(e.device_name||n("header.default_name")),o=m(e.serial||""),a=m(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(a,o),p=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),u=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":k(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=$(Math.max(...n));0===S(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=z(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=z(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(a,s,0,t,o,c),g=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[c,d]of Object.entries(e.sub_devices)){if(d.type===r&&!o)continue;if(d.type===l&&!a)continue;const e=d.type===l?n("subdevice.ev_charger"):d.type===r?n("subdevice.battery"):n("subdevice.fallback"),p=j(d),u=p?t.states[p]:null,g=u&&parseFloat(u.state)||0,h=d.type===r,f=h?I(d):null,_=h?F(d):null,b=h?R(d):null,v=D(d,t,i,new Set([p,f,_,b].filter(Boolean))),w=H(c,0,h,p,f,_);s+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${x(g)} ${y(g)}`:""}\n
\n ${w}\n ${v}\n
\n `}return s}(a,t,o);e.innerHTML=`\n \n ${d}\n ${p}\n ${!1!==o.show_panel?`\n
\n ${u}\n
\n `:""}\n ${g?`
${g}
`:""}\n \n `,this._bindGearClicks(e,a),this._bindToggleClicks(e,a),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await Q(t,a,o,this._powerHistory)}catch{}J(e,t,a,o,this._powerHistory),X(e,t,a,o,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),J(e,this._hass,a,this._config,this._powerHistory),X(e,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const e=O(this._config),t=W(e),n=B(e),i=Date.now(),o=i-e;for(const[e,a]of Object.entries(this._topology.circuits)){const s=E(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(e)||[];c.length>0&&i-c[c.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const Z="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",ee="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",te="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",ne="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ie="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function oe(e,t,n,i,o){return`\n ${i}\n `}class ae{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let a;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:e,return_response:!0});a=n?.response||null}catch{a=null}const s=a?.global_settings||{},r=!0===a?.enabled,l=a?.circuits||{},c=a?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=s.notify_targets||"notify.notify",g=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),h=s.notification_title_template||"SPAN: {name} {alert_type}",f=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),C=Object.entries(c).map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=g.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${m(o)} (${m(e)})`:m(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${C}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const E=e.querySelector("#toggle-all-circuits");E&&!$&&S&&(E.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:o,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const a=e.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:a})})])}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:a})})}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const a=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(a)),n.set(a,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const a=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:o,service:l,service_data:this._serviceData({[c]:a,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,a=n.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:i}:{circuit_id:i});await t.callService(o,s,r),await this.render(e,t)})}}class se{render(e,t){const i=t?`/config/integrations/integration/span_panel#config_entry=${t}`:"/config/integrations/integration/span_panel";e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n
\n `}}class re extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new Y,this._monitoringTab=new ae,this._settingsTab=new se}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===o)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;this._settingsTab.render(e,n);break}}}}customElements.define("span-panel",re),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 88e0444..8def4dc 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -192,7 +192,7 @@ export class SpanPanelCard extends HTMLElement { // ── Unit toggle (A/W) click handler ─────────────────────────────────────── - _onUnitToggle(event) { + async _onUnitToggle(event) { const btn = event.target.closest(".unit-btn"); if (!btn) return; const unit = btn.dataset.unit; @@ -205,8 +205,12 @@ export class SpanPanelCard extends HTMLElement { composed: true, }) ); + this._powerHistory.clear(); + this._historyLoaded = false; this._rendered = false; this._render(); + await this._loadHistory(); + this._updateDOM(); } // ── Toggle click handler ─────────────────────────────────────────────────── diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index a781fb1..2eb461a 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -25,6 +25,7 @@ export class DashboardTab { async render(container, hass, deviceId, config) { this.stop(); this._hass = hass; + this._powerHistory.clear(); this._config = config; try { From c68c188b4f018870adf565e4e45918ca200f35e6 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:57:58 -0700 Subject: [PATCH 035/101] feat: add GRAPH_HORIZONS constants, GraphSettingsCache, and i18n keys --- src/constants.js | 11 ++++++ src/core/graph-settings.js | 79 ++++++++++++++++++++++++++++++++++++++ src/i18n.js | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 src/core/graph-settings.js diff --git a/src/constants.js b/src/constants.js index 27ade76..6e5eccc 100644 --- a/src/constants.js +++ b/src/constants.js @@ -10,6 +10,17 @@ export const DEFAULT_HISTORY_MINUTES = 5; export const DEFAULT_CHART_METRIC = "power"; export const LIVE_SAMPLE_INTERVAL_MS = 1000; +// ── Graph time horizon presets ───────────────────────────────────────────── + +export const DEFAULT_GRAPH_HORIZON = "5m"; + +export const GRAPH_HORIZONS = { + "5m": { ms: 5 * 60 * 1000, refreshMs: 1000, useRealtime: true }, + "1h": { ms: 60 * 60 * 1000, refreshMs: 30000, useRealtime: false }, + "1d": { ms: 24 * 60 * 60 * 1000, refreshMs: 60000, useRealtime: false }, + "1M": { ms: 30 * 24 * 60 * 60 * 1000, refreshMs: 60000, useRealtime: false }, +}; + // ── Domain / type identifiers ─────────────────────────────────────────────── export const INTEGRATION_DOMAIN = "span_panel"; diff --git a/src/core/graph-settings.js b/src/core/graph-settings.js new file mode 100644 index 0000000..2081289 --- /dev/null +++ b/src/core/graph-settings.js @@ -0,0 +1,79 @@ +// src/core/graph-settings.js +import { INTEGRATION_DOMAIN, DEFAULT_GRAPH_HORIZON } from "../constants.js"; + +const GRAPH_SETTINGS_POLL_INTERVAL_MS = 30_000; + +/** + * Caches graph horizon settings fetched via the get_graph_settings service. + * Re-fetches at most every 30 seconds unless invalidated. + */ +export class GraphSettingsCache { + constructor() { + this._settings = null; + this._lastFetch = 0; + this._fetching = false; + } + + /** + * Fetch graph settings, returning cached data if recent. + * @param {object} hass - Home Assistant instance + * @param {string} [configEntryId] - Optional config entry ID + * @returns {Promise} Graph settings or null + */ + async fetch(hass, configEntryId) { + const now = Date.now(); + if (this._fetching) return this._settings; + if (this._settings && now - this._lastFetch < GRAPH_SETTINGS_POLL_INTERVAL_MS) { + return this._settings; + } + + this._fetching = true; + try { + const serviceData = {}; + if (configEntryId) serviceData.config_entry_id = configEntryId; + const resp = await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "get_graph_settings", + service_data: serviceData, + return_response: true, + }); + this._settings = resp?.response || null; + this._lastFetch = now; + } catch { + this._settings = null; + } finally { + this._fetching = false; + } + return this._settings; + } + + /** Force the next fetch() call to re-query the backend. */ + invalidate() { + this._lastFetch = 0; + } + + /** @returns {object|null} Last fetched settings */ + get settings() { + return this._settings; + } + + /** Clear cached settings (e.g., on config change). */ + clear() { + this._settings = null; + this._lastFetch = 0; + } +} + +/** + * Get the effective horizon for a circuit. + * @param {object|null} settings - Full graph settings from get_graph_settings + * @param {string} circuitId - Circuit identifier + * @returns {string} Horizon key (e.g., "5m", "1h") + */ +export function getEffectiveHorizon(settings, circuitId) { + if (!settings) return DEFAULT_GRAPH_HORIZON; + const override = settings.circuits?.[circuitId]; + if (override?.has_override) return override.horizon; + return settings.global_horizon || DEFAULT_GRAPH_HORIZON; +} diff --git a/src/i18n.js b/src/i18n.js index 3b79680..be3e716 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -66,6 +66,22 @@ const translations = { "settings.description": "General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.", "settings.open_link": "Open SPAN Panel Integration Settings", + // Graph time horizon + "horizon.5m": "5 Minutes", + "horizon.1h": "1 Hour", + "horizon.1d": "1 Day", + "horizon.1M": "1 Month", + "settings.graph_horizon_heading": "Graph Time Horizon", + "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", + "settings.global_default": "Global Default", + "settings.default_scale": "Default Scale", + "settings.circuit_graph_scales": "Circuit Graph Scales", + "settings.col.circuit": "Circuit", + "settings.col.scale": "Scale", + "sidepanel.graph_horizon": "Graph Time Horizon", + "sidepanel.graph_horizon_failed": "Graph horizon update failed:", + "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", + // Header "header.default_name": "SPAN Panel", "header.monitoring_settings": "Panel monitoring settings", @@ -205,6 +221,20 @@ const translations = { "settings.description": "La configuraci\u00f3n general de la integraci\u00f3n (nombres de entidades, prefijo de dispositivo, n\u00fameros de circuito) se administra a trav\u00e9s del flujo de opciones de la integraci\u00f3n.", "settings.open_link": "Abrir Configuraci\u00f3n de Integraci\u00f3n SPAN Panel", + "horizon.5m": "5 Minutes", + "horizon.1h": "1 Hour", + "horizon.1d": "1 Day", + "horizon.1M": "1 Month", + "settings.graph_horizon_heading": "Graph Time Horizon", + "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", + "settings.global_default": "Global Default", + "settings.default_scale": "Default Scale", + "settings.circuit_graph_scales": "Circuit Graph Scales", + "settings.col.circuit": "Circuit", + "settings.col.scale": "Scale", + "sidepanel.graph_horizon": "Graph Time Horizon", + "sidepanel.graph_horizon_failed": "Graph horizon update failed:", + "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "Configuraci\u00f3n de monitoreo del panel", "header.site": "Sitio", @@ -330,6 +360,20 @@ const translations = { "settings.description": "Les param\u00e8tres g\u00e9n\u00e9raux de l'int\u00e9gration (noms d'entit\u00e9s, pr\u00e9fixe de l'appareil, num\u00e9ros de circuit) sont g\u00e9r\u00e9s via le flux d'options de l'int\u00e9gration.", "settings.open_link": "Ouvrir les Param\u00e8tres d'Int\u00e9gration SPAN Panel", + "horizon.5m": "5 Minutes", + "horizon.1h": "1 Hour", + "horizon.1d": "1 Day", + "horizon.1M": "1 Month", + "settings.graph_horizon_heading": "Graph Time Horizon", + "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", + "settings.global_default": "Global Default", + "settings.default_scale": "Default Scale", + "settings.circuit_graph_scales": "Circuit Graph Scales", + "settings.col.circuit": "Circuit", + "settings.col.scale": "Scale", + "sidepanel.graph_horizon": "Graph Time Horizon", + "sidepanel.graph_horizon_failed": "Graph horizon update failed:", + "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "Param\u00e8tres de surveillance du panneau", "header.site": "Site", @@ -457,6 +501,20 @@ const translations = { "settings.description": "\u7d71\u5408\u306e\u4e00\u822c\u8a2d\u5b9a\uff08\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u540d\u3001\u30c7\u30d0\u30a4\u30b9\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3001\u56de\u8def\u756a\u53f7\uff09\u306f\u7d71\u5408\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u30d5\u30ed\u30fc\u3067\u7ba1\u7406\u3055\u308c\u307e\u3059\u3002", "settings.open_link": "SPAN Panel\u7d71\u5408\u8a2d\u5b9a\u3092\u958b\u304f", + "horizon.5m": "5 Minutes", + "horizon.1h": "1 Hour", + "horizon.1d": "1 Day", + "horizon.1M": "1 Month", + "settings.graph_horizon_heading": "Graph Time Horizon", + "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", + "settings.global_default": "Global Default", + "settings.default_scale": "Default Scale", + "settings.circuit_graph_scales": "Circuit Graph Scales", + "settings.col.circuit": "Circuit", + "settings.col.scale": "Scale", + "sidepanel.graph_horizon": "Graph Time Horizon", + "sidepanel.graph_horizon_failed": "Graph horizon update failed:", + "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "\u30d1\u30cd\u30eb\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0\u8a2d\u5b9a", "header.site": "\u30b5\u30a4\u30c8", @@ -586,6 +644,20 @@ const translations = { "settings.description": "As configura\u00e7\u00f5es gerais da integra\u00e7\u00e3o (nomes de entidades, prefixo do dispositivo, n\u00fameros de circuito) s\u00e3o gerenciadas atrav\u00e9s do fluxo de op\u00e7\u00f5es da integra\u00e7\u00e3o.", "settings.open_link": "Abrir Configura\u00e7\u00f5es de Integra\u00e7\u00e3o SPAN Panel", + "horizon.5m": "5 Minutes", + "horizon.1h": "1 Hour", + "horizon.1d": "1 Day", + "horizon.1M": "1 Month", + "settings.graph_horizon_heading": "Graph Time Horizon", + "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", + "settings.global_default": "Global Default", + "settings.default_scale": "Default Scale", + "settings.circuit_graph_scales": "Circuit Graph Scales", + "settings.col.circuit": "Circuit", + "settings.col.scale": "Scale", + "sidepanel.graph_horizon": "Graph Time Horizon", + "sidepanel.graph_horizon_failed": "Graph horizon update failed:", + "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "Configura\u00e7\u00f5es de monitoramento do painel", "header.site": "Local", From a10eddfd41c7a51644244a159c92fd88a01d6fb4 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:03:35 -0700 Subject: [PATCH 036/101] feat: expand Settings tab with global + per-circuit graph horizon controls --- src/panel/span-panel.js | 2 +- src/panel/tab-settings.js | 192 +++++++++++++++++++++++++++++++++++++- 2 files changed, 189 insertions(+), 5 deletions(-) diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 58ec507..fdd0a5e 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -226,7 +226,7 @@ export class SpanPanelElement extends HTMLElement { container.innerHTML = ""; const selectedDevice = this._panels.find(p => p.id === this._selectedPanelId); const configEntryId = selectedDevice?.config_entries?.[0] || null; - this._settingsTab.render(container, configEntryId); + await this._settingsTab.render(container, this._hass, configEntryId, this._selectedPanelId); break; } } diff --git a/src/panel/tab-settings.js b/src/panel/tab-settings.js index 3cb0cef..5b2fa5d 100644 --- a/src/panel/tab-settings.js +++ b/src/panel/tab-settings.js @@ -1,8 +1,97 @@ +import { INTEGRATION_DOMAIN, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; +import { escapeHtml } from "../helpers/sanitize.js"; import { t } from "../i18n.js"; +function horizonOptions(selectedKey) { + return Object.keys(GRAPH_HORIZONS) + .map(key => ``) + .join(""); +} + +const SELECT_STYLE = ` + background:var(--secondary-background-color,#333); + border:1px solid var(--divider-color); + color:var(--primary-text-color); + border-radius:4px;padding:4px 8px;font-size:0.85em; +`; + export class SettingsTab { - render(container, configEntryId) { - const href = configEntryId ? `/config/integrations/integration/span_panel#config_entry=${configEntryId}` : "/config/integrations/integration/span_panel"; + constructor() { + this._debounceTimers = new Map(); + this._configEntryId = null; + this._deviceId = null; + } + + async render(container, hass, configEntryId, deviceId) { + if (configEntryId !== undefined) this._configEntryId = configEntryId; + if (deviceId !== undefined) this._deviceId = deviceId; + + let graphSettings; + try { + const serviceData = {}; + if (this._configEntryId) serviceData.config_entry_id = this._configEntryId; + const resp = await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "get_graph_settings", + service_data: serviceData, + return_response: true, + }); + graphSettings = resp?.response || null; + } catch { + graphSettings = null; + } + + let topology = null; + try { + if (this._deviceId) { + topology = await hass.callWS({ + type: `${INTEGRATION_DOMAIN}/panel_topology`, + device_id: this._deviceId, + }); + } + } catch { + topology = null; + } + + const globalHorizon = graphSettings?.global_horizon ?? DEFAULT_GRAPH_HORIZON; + const circuitSettings = graphSettings?.circuits ?? {}; + + const circuitEntries = topology ? Object.entries(topology.circuits || {}).sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || "")) : []; + + const circuitRows = circuitEntries + .map(([uuid, circuit]) => { + const name = escapeHtml(circuit.name || uuid); + const circuitData = circuitSettings[uuid] || {}; + const effectiveHorizon = circuitData.horizon ?? globalHorizon; + const hasOverride = circuitData.has_override === true; + const safeUuid = escapeHtml(uuid); + return ` + + ${name} + + + + + ${ + hasOverride + ? `` + : "" + } + + + `; + }) + .join(""); + + const href = this._configEntryId + ? `/config/integrations/integration/${INTEGRATION_DOMAIN}#config_entry=${this._configEntryId}` + : `/config/integrations/integration/${INTEGRATION_DOMAIN}`; container.innerHTML = `
@@ -10,11 +99,106 @@ export class SettingsTab {

${t("settings.description")}

- + ${t("settings.open_link")} → + +
+ +

${t("settings.graph_horizon_heading")}

+ +
+
+ ${t("settings.global_default")} + +
+
+ + ${ + circuitEntries.length > 0 + ? ` +

${t("settings.circuit_graph_scales")}

+ + + + + + + + + + ${circuitRows} + +
${t("settings.col.circuit")}${t("settings.col.scale")}
+ ` + : "" + }
`; + + this._bindGlobalHorizon(container, hass); + this._bindCircuitHorizons(container, hass); + this._bindResetButtons(container, hass); + } + + _serviceData(data) { + if (this._configEntryId) data.config_entry_id = this._configEntryId; + return data; + } + + _bindGlobalHorizon(container, hass) { + const select = container.querySelector("#global-horizon"); + if (!select) return; + select.addEventListener("change", async () => { + await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "set_graph_time_horizon", + service_data: this._serviceData({ horizon: select.value }), + }); + container.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + await this.render(container, hass); + }); + } + + _bindCircuitHorizons(container, hass) { + for (const select of container.querySelectorAll(".horizon-select")) { + select.addEventListener("change", () => { + const uuid = select.dataset.circuit; + const key = `circuit-${uuid}`; + clearTimeout(this._debounceTimers.get(key)); + this._debounceTimers.set( + key, + setTimeout(async () => { + await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "set_circuit_graph_horizon", + service_data: this._serviceData({ circuit_id: uuid, horizon: select.value }), + }); + container.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + await this.render(container, hass); + }, 500) + ); + }); + } + } + + _bindResetButtons(container, hass) { + for (const btn of container.querySelectorAll(".reset-btn")) { + btn.addEventListener("click", async () => { + const uuid = btn.dataset.circuit; + await hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service: "clear_circuit_graph_horizon", + service_data: this._serviceData({ circuit_id: uuid }), + }); + container.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + await this.render(container, hass); + }); + } } } From 888494227ffea8ff8b3b1ad945c0819a48c4ad35 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:07:55 -0700 Subject: [PATCH 037/101] feat: add graph horizon section to side panel with cache sync --- src/core/side-panel.js | 89 +++++++++++++++++++++++++++++++++++++- src/panel/tab-dashboard.js | 16 ++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/core/side-panel.js b/src/core/side-panel.js index d14c2fe..475437b 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -1,6 +1,6 @@ // src/core/side-panel.js import { escapeHtml } from "../helpers/sanitize.js"; -import { INTEGRATION_DOMAIN, SHEDDING_PRIORITIES } from "../constants.js"; +import { INTEGRATION_DOMAIN, SHEDDING_PRIORITIES, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; import { t } from "../i18n.js"; const DEBOUNCE_MS = 500; @@ -290,6 +290,7 @@ class SpanSidePanel extends HTMLElement { this._renderRelaySection(body, cfg); this._renderSheddingSection(body, cfg); + this._renderGraphHorizonSection(body, cfg); this._renderMonitoringSection(body, cfg); } @@ -389,6 +390,92 @@ class SpanSidePanel extends HTMLElement { body.appendChild(section); } + // ── Graph horizon section ────────────────────────────────────────── + + _renderGraphHorizonSection(body, cfg) { + const section = document.createElement("div"); + section.className = "section"; + + const sectionLabel = document.createElement("div"); + sectionLabel.className = "section-label"; + sectionLabel.textContent = t("sidepanel.graph_horizon"); + section.appendChild(sectionLabel); + + const graphInfo = cfg.graphHorizonInfo; + const hasOverride = graphInfo?.has_override === true; + const currentHorizon = graphInfo?.horizon || DEFAULT_GRAPH_HORIZON; + + // Global / Custom radio + const radioGroup = document.createElement("div"); + radioGroup.className = "radio-group"; + radioGroup.innerHTML = ` + + + `; + section.appendChild(radioGroup); + + // Horizon dropdown + const selectWrap = document.createElement("div"); + selectWrap.dataset.role = "graph-horizon-fields"; + selectWrap.style.display = hasOverride ? "block" : "none"; + + const row = document.createElement("div"); + row.className = "field-row"; + + const label = document.createElement("span"); + label.className = "field-label"; + label.textContent = t("settings.default_scale"); + + const selectEl = document.createElement("select"); + selectEl.dataset.role = "graph-horizon-select"; + for (const key of Object.keys(GRAPH_HORIZONS)) { + const opt = document.createElement("option"); + opt.value = key; + opt.textContent = t(`horizon.${key}`); + if (key === currentHorizon) opt.selected = true; + selectEl.appendChild(opt); + } + + row.appendChild(label); + row.appendChild(selectEl); + selectWrap.appendChild(row); + section.appendChild(selectWrap); + + // Event: radio change + const radios = radioGroup.querySelectorAll('input[type="radio"]'); + for (const radio of radios) { + radio.addEventListener("change", () => { + const isCustom = radio.value === "custom" && radio.checked; + selectWrap.style.display = isCustom ? "block" : "none"; + if (!isCustom && radio.checked) { + const circuitId = cfg.uuid; + this._callDomainService("clear_circuit_graph_horizon", { circuit_id: circuitId }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${err.message ?? err}`)); + } + }); + } + + // Event: dropdown change (debounced) + selectEl.addEventListener("change", () => { + this._debounce("graph-horizon", DEBOUNCE_MS, () => { + const circuitId = cfg.uuid; + this._callDomainService("set_circuit_graph_horizon", { + circuit_id: circuitId, + horizon: selectEl.value, + }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); + }); + }); + + body.appendChild(section); + } + // ── Monitoring section ────────────────────────────────────────────── _renderMonitoringSection(body, cfg) { diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 2eb461a..57ff5c8 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -5,6 +5,7 @@ import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; import { loadHistory } from "../core/history-loader.js"; import { MonitoringStatusCache, buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; +import { GraphSettingsCache } from "../core/graph-settings.js"; import { CARD_STYLES } from "../card/card-styles.js"; import { getHistoryDurationMs, recordSample, getMaxHistoryPoints, getMinGapMs } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; @@ -17,6 +18,7 @@ export class DashboardTab { this._panelSize = 0; this._powerHistory = new Map(); this._monitoringCache = new MonitoringStatusCache(); + this._graphSettingsCache = new GraphSettingsCache(); this._updateInterval = null; this._hass = null; this._config = null; @@ -38,6 +40,7 @@ export class DashboardTab { } await this._monitoringCache.fetch(hass); + await this._graphSettingsCache.fetch(hass); const topo = this._topology; const totalRows = Math.ceil(this._panelSize / 2); @@ -70,6 +73,10 @@ export class DashboardTab { this._bindToggleClicks(container, topo); container.addEventListener("side-panel-closed", () => { this._monitoringCache.invalidate(); + this._graphSettingsCache.invalidate(); + }); + container.addEventListener("graph-settings-changed", () => { + this._graphSettingsCache.invalidate(); }); try { @@ -153,14 +160,21 @@ export class DashboardTab { const circuit = topology.circuits[uuid]; if (!circuit) return; - // Always fetch fresh monitoring data before opening side panel + // Always fetch fresh monitoring and graph settings data before opening side panel await this._monitoringCache.fetch(this._hass); const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; + await this._graphSettingsCache.fetch(this._hass); + const graphSettings = this._graphSettingsCache.settings; + const graphHorizonInfo = graphSettings?.circuits?.[uuid] + ? graphSettings.circuits[uuid] + : { horizon: graphSettings?.global_horizon || "5m", has_override: false }; + sidePanel.open({ ...circuit, uuid, monitoringInfo, + graphHorizonInfo, }); }); } From 3c6cf4c986ed2699d2186c6099a1e8a9972239ed Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:13:53 -0700 Subject: [PATCH 038/101] feat: adaptive history loading with per-circuit horizons and recorder refresh --- src/core/history-loader.js | 58 ++++++++++++++++++--------- src/helpers/history.js | 12 +++++- src/panel/tab-dashboard.js | 80 ++++++++++++++++++++++++++++++++++---- 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/src/core/history-loader.js b/src/core/history-loader.js index 0045a00..68c3d0d 100644 --- a/src/core/history-loader.js +++ b/src/core/history-loader.js @@ -1,4 +1,4 @@ -import { getHistoryDurationMs, getMaxHistoryPoints, getMinGapMs, deduplicateAndTrim } from "../helpers/history.js"; +import { getHistoryDurationMs, getMaxHistoryPoints, getMinGapMs, deduplicateAndTrim, getHorizonDurationMs } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity } from "../helpers/entity-finder.js"; import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_KEY_PREFIX } from "../constants.js"; @@ -112,36 +112,56 @@ export function collectSubDeviceEntityIds(topology) { /** * Load historical power data from HA recorder into the powerHistory Map. + * Supports per-circuit horizons by grouping circuits by their effective duration. * * @param {object} hass * @param {object} topology - * @param {object} config + * @param {object} config - card config (fallback for duration) * @param {Map} powerHistory - mutated in place + * @param {Map} [horizonMap] - optional uuid → horizon key map */ -export async function loadHistory(hass, topology, config, powerHistory) { +export async function loadHistory(hass, topology, config, powerHistory, horizonMap) { if (!topology || !hass) return; - const durationMs = getHistoryDurationMs(config); - const entityIds = []; - const uuidByEntity = new Map(); + // Group circuits by effective duration + const groups = new Map(); // durationMs → { entityIds: [], uuidByEntity: Map } for (const [uuid, circuit] of Object.entries(topology.circuits)) { const eid = getCircuitChartEntity(circuit, config); - if (eid) { - entityIds.push(eid); - uuidByEntity.set(eid, uuid); - } - } + if (!eid) continue; - _collectSubDeviceEntityIdsInto(topology, entityIds, uuidByEntity); - - if (entityIds.length === 0) return; + let durationMs; + if (horizonMap && horizonMap.has(uuid)) { + durationMs = getHorizonDurationMs(horizonMap.get(uuid)); + } else { + durationMs = getHistoryDurationMs(config); + } - const useStatistics = durationMs > 2 * 60 * 60 * 1000; + if (!groups.has(durationMs)) { + groups.set(durationMs, { entityIds: [], uuidByEntity: new Map() }); + } + const group = groups.get(durationMs); + group.entityIds.push(eid); + group.uuidByEntity.set(eid, uuid); + } - if (useStatistics) { - await loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory); - } else { - await loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory); + // Add sub-device entities to the default duration group + const defaultDurationMs = getHistoryDurationMs(config); + if (!groups.has(defaultDurationMs)) { + groups.set(defaultDurationMs, { entityIds: [], uuidByEntity: new Map() }); + } + _collectSubDeviceEntityIdsInto(topology, groups.get(defaultDurationMs).entityIds, groups.get(defaultDurationMs).uuidByEntity); + + // Load each group in parallel + const promises = []; + for (const [durationMs, group] of groups) { + if (group.entityIds.length === 0) continue; + const useStatistics = durationMs > 2 * 60 * 60 * 1000; + if (useStatistics) { + promises.push(loadStatisticsHistory(hass, group.entityIds, group.uuidByEntity, durationMs, powerHistory)); + } else { + promises.push(loadRawHistory(hass, group.entityIds, group.uuidByEntity, durationMs, powerHistory)); + } } + await Promise.all(promises); } diff --git a/src/helpers/history.js b/src/helpers/history.js index e763995..57dafaa 100644 --- a/src/helpers/history.js +++ b/src/helpers/history.js @@ -1,4 +1,4 @@ -import { DEFAULT_HISTORY_DAYS, DEFAULT_HISTORY_HOURS, DEFAULT_HISTORY_MINUTES } from "../constants.js"; +import { DEFAULT_HISTORY_DAYS, DEFAULT_HISTORY_HOURS, DEFAULT_HISTORY_MINUTES, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; export function getHistoryDurationMs(config) { const hasAny = config.history_days !== undefined || config.history_hours !== undefined || config.history_minutes !== undefined; @@ -9,6 +9,16 @@ export function getHistoryDurationMs(config) { return Math.max(total, 60000); } +/** + * Get duration in ms for a horizon key. + * @param {string} horizonKey - e.g. "5m", "1h", "1d", "1M" + * @returns {number} Duration in milliseconds + */ +export function getHorizonDurationMs(horizonKey) { + const h = GRAPH_HORIZONS[horizonKey]; + return h ? h.ms : GRAPH_HORIZONS[DEFAULT_GRAPH_HORIZON].ms; +} + export function getMaxHistoryPoints(durationMs) { const seconds = durationMs / 1000; if (seconds <= 600) return Math.ceil(seconds); diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 57ff5c8..656d0b0 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -7,9 +7,9 @@ import { loadHistory } from "../core/history-loader.js"; import { MonitoringStatusCache, buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; import { GraphSettingsCache } from "../core/graph-settings.js"; import { CARD_STYLES } from "../card/card-styles.js"; -import { getHistoryDurationMs, recordSample, getMaxHistoryPoints, getMinGapMs } from "../helpers/history.js"; +import { getHistoryDurationMs, recordSample, getMaxHistoryPoints, getMinGapMs, getHorizonDurationMs } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; -import { LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; +import { LIVE_SAMPLE_INTERVAL_MS, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; import "../core/side-panel.js"; export class DashboardTab { @@ -20,6 +20,8 @@ export class DashboardTab { this._monitoringCache = new MonitoringStatusCache(); this._graphSettingsCache = new GraphSettingsCache(); this._updateInterval = null; + this._recorderRefreshInterval = null; + this._horizonMap = new Map(); this._hass = null; this._config = null; } @@ -43,6 +45,17 @@ export class DashboardTab { await this._graphSettingsCache.fetch(hass); const topo = this._topology; + + // Build per-circuit horizon map + this._horizonMap = new Map(); + const graphSettings = this._graphSettingsCache.settings; + if (topo?.circuits) { + for (const uuid of Object.keys(topo.circuits)) { + const override = graphSettings?.circuits?.[uuid]; + const horizon = override?.has_override ? override.horizon : graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + this._horizonMap.set(uuid, horizon); + } + } const totalRows = Math.ceil(this._panelSize / 2); const durationMs = getHistoryDurationMs(config); const monitoringStatus = this._monitoringCache.status; @@ -75,12 +88,33 @@ export class DashboardTab { this._monitoringCache.invalidate(); this._graphSettingsCache.invalidate(); }); - container.addEventListener("graph-settings-changed", () => { + container.addEventListener("graph-settings-changed", async () => { this._graphSettingsCache.invalidate(); + await this._graphSettingsCache.fetch(this._hass); + + // Rebuild horizon map + const newSettings = this._graphSettingsCache.settings; + if (topo?.circuits) { + for (const uuid of Object.keys(topo.circuits)) { + const override = newSettings?.circuits?.[uuid]; + const horizon = override?.has_override ? override.horizon : newSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + this._horizonMap.set(uuid, horizon); + } + } + + // Reload all history with new horizons + this._powerHistory.clear(); + try { + await loadHistory(this._hass, topo, this._config, this._powerHistory, this._horizonMap); + } catch { + // Will populate on next refresh + } + updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory); + updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory); }); try { - await loadHistory(hass, topo, config, this._powerHistory); + await loadHistory(hass, topo, config, this._powerHistory, this._horizonMap); } catch { // Charts will populate live } @@ -95,17 +129,38 @@ export class DashboardTab { updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory); updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory); }, LIVE_SAMPLE_INTERVAL_MS); + + // Periodic recorder refresh for non-realtime horizons + this._recorderRefreshInterval = setInterval(async () => { + if (!this._topology || !this._hass) return; + const nonRealtimeMap = new Map(); + for (const [uuid, horizon] of this._horizonMap) { + if (!GRAPH_HORIZONS[horizon]?.useRealtime) { + nonRealtimeMap.set(uuid, horizon); + } + } + if (nonRealtimeMap.size === 0) return; + // Clear and reload history for non-realtime circuits + for (const uuid of nonRealtimeMap.keys()) { + this._powerHistory.delete(uuid); + } + try { + await loadHistory(this._hass, this._topology, this._config, this._powerHistory, nonRealtimeMap); + updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory); + } catch { + // Recorder data will refresh on next interval + } + }, 30000); } _recordSamples() { if (!this._topology || !this._hass) return; - const durationMs = getHistoryDurationMs(this._config); - const maxPoints = getMaxHistoryPoints(durationMs); - const minGap = getMinGapMs(durationMs); const now = Date.now(); - const cutoff = now - durationMs; for (const [uuid, circuit] of Object.entries(this._topology.circuits)) { + const horizon = this._horizonMap?.get(uuid) || DEFAULT_GRAPH_HORIZON; + if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; + const eid = getCircuitChartEntity(circuit, this._config); if (!eid) continue; const state = this._hass.states[eid]; @@ -113,6 +168,11 @@ export class DashboardTab { const val = parseFloat(state.state); if (isNaN(val)) continue; + const durationMs = getHorizonDurationMs(horizon); + const maxPoints = getMaxHistoryPoints(durationMs); + const minGap = getMinGapMs(durationMs); + const cutoff = now - durationMs; + const hist = this._powerHistory.get(uuid) || []; if (hist.length > 0 && now - hist[hist.length - 1].time < minGap) continue; @@ -184,5 +244,9 @@ export class DashboardTab { clearInterval(this._updateInterval); this._updateInterval = null; } + if (this._recorderRefreshInterval) { + clearInterval(this._recorderRefreshInterval); + this._recorderRefreshInterval = null; + } } } From a1d041181e5d5ce173a98443dc2f1db99b7f1b29 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:16:21 -0700 Subject: [PATCH 039/101] feat: scale chart x-axis per circuit horizon --- src/core/dom-updater.js | 9 +++++---- src/panel/tab-dashboard.js | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index 84c9537..0b8b8ed 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -3,7 +3,7 @@ import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format. import { t } from "../i18n.js"; import { getChartMetric } from "../helpers/chart.js"; import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; -import { getHistoryDurationMs } from "../helpers/history.js"; +import { getHistoryDurationMs, getHorizonDurationMs } from "../helpers/history.js"; import { updateChart } from "../chart/chart-update.js"; // ── Header stats ─────────────────────────────────────────────────────────── @@ -104,10 +104,10 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption) { // ── Exported updaters ────────────────────────────────────────────────────── -export function updateCircuitDOM(root, hass, topology, config, powerHistory) { +export function updateCircuitDOM(root, hass, topology, config, powerHistory, horizonMap) { if (!root || !topology || !hass) return; - const durationMs = getHistoryDurationMs(config); + const defaultDurationMs = getHistoryDurationMs(config); let totalConsumption = 0; for (const [, circuit] of Object.entries(topology.circuits)) { @@ -176,7 +176,8 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory) { if (chartContainer) { const history = powerHistory.get(uuid) || []; const h = slot.classList.contains("circuit-col-span") ? 200 : 100; - updateChart(chartContainer, hass, history, durationMs, chartMetric, isProducer, h, circuit.breaker_rating_a); + const circuitDuration = horizonMap?.has(uuid) ? getHorizonDurationMs(horizonMap.get(uuid)) : defaultDurationMs; + updateChart(chartContainer, hass, history, circuitDuration, chartMetric, isProducer, h, circuit.breaker_rating_a); } } } diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 656d0b0..45261a4 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -109,7 +109,7 @@ export class DashboardTab { } catch { // Will populate on next refresh } - updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory); + updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory); }); @@ -120,13 +120,13 @@ export class DashboardTab { } // Initial DOM update with history data - updateCircuitDOM(container, hass, topo, config, this._powerHistory); + updateCircuitDOM(container, hass, topo, config, this._powerHistory, this._horizonMap); updateSubDeviceDOM(container, hass, topo, config, this._powerHistory); // Start live update loop this._updateInterval = setInterval(() => { this._recordSamples(); - updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory); + updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory); }, LIVE_SAMPLE_INTERVAL_MS); @@ -146,7 +146,7 @@ export class DashboardTab { } try { await loadHistory(this._hass, this._topology, this._config, this._powerHistory, nonRealtimeMap); - updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory); + updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); } catch { // Recorder data will refresh on next interval } From b2f3ee614e7dc4523c5ee3ce636a23a721945795 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:17:19 -0700 Subject: [PATCH 040/101] feat: synchronize graph settings between Settings tab and side panel --- src/panel/span-panel.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index fdd0a5e..9e75423 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -163,6 +163,15 @@ export class SpanPanelElement extends HTMLElement { this._bindUnitToggle(); this._bindTabNavigation(); + + // Sync: if graph settings change (from side panel or settings tab), + // re-render settings tab if it's visible + this.shadowRoot.addEventListener("graph-settings-changed", () => { + if (this._activeTab === "settings") { + this._renderTab(); + } + }); + this._renderTab(); } From 57c35ef7a7b9d97f2e8bab68bc7f18f759e5f387 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:21:50 -0700 Subject: [PATCH 041/101] feat: complete graph time horizon frontend support Apply graph horizon support to standalone card mode: fetch per-circuit horizons via GraphSettingsCache, skip non-realtime circuits from live sampling, and pass horizonMap to DOM updater and history loader. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/span-panel-card.js | 44 +++++++++++++++++++++++++++++++------ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index b585963..39bd4d2 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="span_panel",o="CLOSED",a="pv",s="bess",r="evse",c="sub_",l={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},d={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:l.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},p="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function g(e){return String(e).replace(/[&<>"']/g,e=>h[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function f(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function _(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function v(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function b(e){return l[e.chart_metric]||l[n]}function y(e,t){const n=function(e){return b(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}const w=l.power;function x(e){return w.unit(e)}function C(e){return(e<0?"-":"")+w.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function E(e){return e%2==0?1:0}function $(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":E(t)===E(n)?"col-span":"row-span"}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:i,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function N(e,n,i,s,r,c,l,d,h,m){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,y=n.device_type===a||v<0,w=n.entities?.switch,S=w?l.states[w]:null,k=S?"on"===S.state:(_?.attributes?.relay_state||n.relay_state)===o,E=n.breaker_rating_a,$=E?`${Math.round(E)}A`:"",P=g(n.name||t("grid.unknown")),N=b(d);let M;if("current"===N.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${N.format(i)}A`}else M=`${C(v)}${x(v)}`;const A=u[m||"unknown"]||u.unknown,z=``,T=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),L=T?p:"#555",R=``;let D="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);D=`${Math.round(e)}%`}const F=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${P}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(k?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${z}\n ${D}\n ${R}\n
\n
\n
\n `}function M(e,t){return`\n
\n \n
\n `}const A={names:["power","battery power"],suffixes:["_power"]},z={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function R(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return R(e,A)}function F(e){return R(e,z)}function j(e){return R(e,T)}function H(e){return R(e,L)}function I(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${g(c)}:\n ${g(d)}\n
\n `}return a}function O(e,n,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:t("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:t("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${g(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function W(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=f(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,v(t,r,c))}}}function q(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===s&&(e.soc=F(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}async function V(e,t,n,i){if(!t||!e)return;const o=m(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=y(i,n);t&&(a.push(t),s.set(t,e))}if(function(e,t,n){for(const{entityId:i,key:o}of q(e))t.push(i),n.set(i,o)}(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await W(e,a,s,o,i)}function B(e,t,i,o,a,s,r,c){const{options:d,series:u}=function(e,t,i,o,a){i||(i=l[n]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,c=Date.now(),d=c-t,u=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return u&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[d,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[d,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:h}}(i,o,a,s,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=d,p.data=u}function G(e,n,i,s,r){if(!e||!i||!n)return;const c=m(s);let l=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==a&&(l+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=S(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),u=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),u&&(u.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=S(e)}else p.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,s,l);const d=b(s),p="current"===d.entityRole;for(const[s,l]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${s}"]`);if(!i)continue;const h=l.entities?.power,g=h?n.states[h]:null,m=g&&parseFloat(g.state)||0,f=l.device_type===a||m<0,_=l.entities?.switch,v=_?n.states[_]:null,b=v?"on"===v.state:(g?.attributes?.relay_state||l.relay_state)===o,y=i.querySelector(".power-value");if(y)if(p){const e=l.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;y.innerHTML=`${d.format(i)}A`}else y.innerHTML=`${C(m)}${x(m)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(b?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=t(b?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!b),i.classList.toggle("circuit-producer",f);const S=l.entities?.select,k=S?n.states[S]:null,E=k?k.state:"unknown",$=u[E]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",$.icon),P.style.color=$.color,P.title=$.label());const N=i.querySelector(".chart-container");if(N){B(N,n,r.get(s)||[],c,d,f,i.classList.contains("circuit-col-span")?200:100,l.breaker_rating_a)}}}function U(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const J=Object.keys(u).filter(e=>"unknown"!==e);class K extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=t("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${g(String(t.breaker_rating_a))}A · ${g(String(t.voltage))}V · Tabs [${g(String(t.tabs))}]`,i=this._createHeader(g(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of J){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,u=document.createElement("div");u.className="radio-group",u.innerHTML=`\n \n \n `,l.appendChild(u);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",m,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",n)),l.appendChild(p),s.addEventListener("change",()=>{const e=s.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=u.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,n,i,o,a,s,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const u=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(a),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const h=document.createElement("span");return h.textContent=s,u.appendChild(p),u.appendChild(h),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(u),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(i,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",K);class X extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new P}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._monitoringCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const o=await e.callWS({type:`${i}/panel_topology`,device_id:n}),a=o.panel_size||U(o.circuits);if(!a)throw new Error(t("card.topology_error"));return{topology:o,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:a}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[o,a]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=o.find(e=>e.id===n)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=a.filter(e=>e.device_id===n),c=o.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=a.filter(e=>l.has(e.device_id)),u={},p=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!a.every(Number.isFinite))continue;const s=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(s[e])){r=s[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");u[r]={tabs:a,name:c,voltage:n.attributes.voltage||(2===a.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(s.identifiers)for(const e of s.identifiers)e[0]===i&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=U(u)),!g)throw new Error(t("card.panel_size_error"));const m={};for(const t of c){const n=a.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),o=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),s={};for(const t of n)s[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};m[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":o?"evse":"unknown",entities:s}}return{topology:{serial:h,firmware:s.sw_version||"",panel_size:g,device_id:n,device_name:s.name_by_user||s.name||t("header.default_name"),circuits:u,sub_devices:m},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await V(this._hass,this._topology,this._config,this._powerHistory),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now(),t=e-this._durationMs,n=f(this._durationMs);for(const[i,o]of Object.entries(this._topology.circuits)){const a=y(o,this._config);if(!a)continue;const s=this._hass.states[a],r=s&&parseFloat(s.state)||0;_(this._powerHistory,i,r,e,t,n)}for(const{entityId:i,key:o}of q(this._topology)){const a=this._hass.states[i],s=a&&parseFloat(a.state)||0;_(this._powerHistory,o,s,e,t,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){G(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory),function(e,t,n,i,o){if(!n.sub_devices)return;const a=m(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${C(i)} ${x(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let s=d.power;n.endsWith("_soc")?s=d.soc:n.endsWith("_soe")&&(s=d.soe);const r=!!e.closest(".bess-chart-col");B(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const a=o.entities?.switch;if(!a)return;const s=this._hass.states[a];if(!s)return void console.warn("SPAN Panel: switch entity not found:",a);const r="on"===s.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}_onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=t.dataset.uuid;if(!i||!this._topology)return;const o=this._topology.circuits[i];if(!o)return;const a=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;n.open({...o,uuid:i,monitoringInfo:a})}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${g(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=g(e.device_name||t("header.default_name")),o=g(e.serial||""),a=g(e.firmware||""),s="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,u=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(n,this._config)),a=this._monitoringCache.status,c=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${a>0?`${a} ${t(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${t(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(a),l=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":$(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===E(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let u="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),p=s.get(n);if(u+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);u+=N(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),u+=`
${n}
`;continue}if(!c.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(u+=M(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);u+=N(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(u+=M(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);u+=N(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}u+=`
${n}
`}return u}(n,i,0,e,this._config,a),d=function(e,n,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let c="";if(!e.sub_devices)return c;for(const[l,d]of Object.entries(e.sub_devices)){if(d.type===s&&!o)continue;if(d.type===r&&!a)continue;const e=d.type===r?t("subdevice.ev_charger"):d.type===s?t("subdevice.battery"):t("subdevice.fallback"),u=D(d),p=u?n.states[u]:null,h=p&&parseFloat(p.state)||0,m=d.type===s,f=m?F(d):null,_=m?j(d):null,v=m?H(d):null,b=I(d,n,i,new Set([u,f,_,v].filter(Boolean))),y=O(l,0,m,u,f,_);c+=`\n
\n
\n ${g(e)}\n ${g(d.name||"")}\n ${u?`${C(h)} ${x(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return c}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${c}\n ${!1!==this._config.show_panel?`\n
\n ${l}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class Q extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===i)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===i)?.[1]||"",o=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${o} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.panel_label"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_window"),s.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const a=document.createElement("input");a.type="number",a.min=t,a.max=n,a.value=String(e),a.style.cssText=c;const s=document.createElement("span");return s.textContent=i,s.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(a),o.appendChild(s),{wrap:o,input:a}},d=parseInt(this._config.history_days)||0,u=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(u,"0","23",t("editor.hours")),m=l(p,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(m.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),m.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(m.wrap),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=m.input}_buildMetricSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_metric"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const a=document.createElement("label");a.textContent=t("editor.visible_sections"),a.style.cssText=n,o.appendChild(a);const s=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of s){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let a=null;e.subDeviceType&&(a=document.createElement("div"),a.style.cssText="padding-left: 26px;",a.style.display=n.checked?"block":"none",o.appendChild(a),this._entityContainers[e.subDeviceType]=a),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},a&&(a.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const a=document.createElement("div");a.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const s=document.createElement("input");s.type="checkbox",s.checked=!0===t[i],s.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",a.appendChild(s),a.appendChild(r),e.appendChild(a),s.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};s.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${i}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(l)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",X),customElements.define("span-panel-card-editor",Q),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",s="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,n,i,o,a,l,c,d,p,u){const f=n.entities?.power,_=f?c.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===s,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},F={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function I(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return I(e,L)}function O(e){return I(e,R)}function G(e){return I(e,H)}function W(e){return I(e,F)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${m(l)}:\n ${m(d)}\n
\n `}return a}function B(e,n,i,o,a,s){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!a},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!s},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=O(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):_(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=_(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(V(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,a,s,r,l){const{options:c,series:d}=function(e,t,i,o,a){i||(i=p[n]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,a,l){if(!e||!i||!n)return;const c=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===s,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_);const S=d.entities?.select,k=S?n.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=i.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=i.querySelector(".chart-container");if(N){const e=a.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=t("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const a=document.createElement("div");a.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.graph_horizon"),a.appendChild(s);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,a.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const n=document.createElement("option");n.value=e,n.textContent=t(`horizon.${e}`),e===c&&(n.selected=!0),g.appendChild(n)}u.appendChild(h),u.appendChild(g),p.appendChild(u),a.appendChild(p);const f=d.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=n.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(a)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,n,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${a}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=i.find(e=>e.id===n)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!a.every(Number.isFinite))continue;const s=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(s[e])){r=s[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:a,name:l,voltage:n.attributes.voltage||(2===a.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(s.identifiers)for(const e of s.identifiers)e[0]===a&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),a=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),s={};for(const t of n)s[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":a?"evse":"unknown",entities:s}}return{topology:{serial:h,firmware:s.sw_version||"",panel_size:g,device_id:n,device_name:s.name_by_user||s.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const a=this._horizonMap?.get(t)||i;if(!o[a]?.useRealtime)continue;const s=C(n,this._config);if(!s)continue;const r=this._hass.states[s],l=r&&parseFloat(r.state)||0,c=v(a),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=_(this._config),n=b(t),a=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],s=o&&parseFloat(o.state)||0;y(this._powerHistory,i,s,e,a,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const a=_(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=u.power;n.endsWith("_soc")?s=u.soc:n.endsWith("_soe")&&(s=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const a=o.entities?.switch;if(!a)return;const s=this._hass.states[a];if(!s)return void console.warn("SPAN Panel: switch entity not found:",a);const r="on"===s.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}_onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=t.dataset.uuid;if(!i||!this._topology)return;const o=this._topology.circuits[i];if(!o)return;const a=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;n.open({...o,uuid:i,monitoringInfo:a})}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),a=m(e.firmware||""),s="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(n,this._config)),a=this._monitoringCache.status,s=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${a>0?`${a} ${t(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${t(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(a),r=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=T(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=T(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,a),d=function(e,n,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!a)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?O(d):null,_=g?G(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=B(r,0,g,p,f,_);s+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${z(h)} ${E(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${s}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===a)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===a)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.panel_label"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_window"),s.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const a=document.createElement("input");a.type="number",a.min=t,a.max=n,a.value=String(e),a.style.cssText=l;const s=document.createElement("span");return s.textContent=i,s.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(a),o.appendChild(s),{wrap:o,input:a}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_metric"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const a=document.createElement("label");a.textContent=t("editor.visible_sections"),a.style.cssText=n,o.appendChild(a);const s=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of s){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let a=null;e.subDeviceType&&(a=document.createElement("div"),a.style.cssText="padding-left: 26px;",a.style.display=n.checked?"block":"none",o.appendChild(a),this._entityContainers[e.subDeviceType]=a),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},a&&(a.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const a=document.createElement("div");a.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const s=document.createElement("input");s.type="checkbox",s.checked=!0===t[i],s.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",a.appendChild(s),a.appendChild(r),e.appendChild(a),s.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};s.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${a}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 1b3b51e..9991f7e 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="span_panel",a="CLOSED",s="pv",r="bess",l="evse",c="sub_",d={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:d.power},u={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},g="#ff9800",h={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>h[e])}const f=Object.keys(u).filter(e=>"unknown"!==e);class _ extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of f){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const g=r?.continuous_threshold_pct??80,h=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",g,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",h,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=s,p.appendChild(u),p.appendChild(g),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(o,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function b(e,t){const i=await e.callWS({type:`${o}/panel_topology`,device_id:t}),a=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!a)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:a}}customElements.define("span-side-panel",_);const v=d.power;function y(e){return v.unit(e)}function x(e){return(e<0?"-":"")+v.format(e)}function w(e){return(Math.abs(e)/1e3).toFixed(1)}function $(e){return Math.ceil(e/2)}function S(e){return e%2==0?1:0}function k(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return $(t)===$(n)?"row-span":S(t)===S(n)?"col-span":"row-span"}function C(e){return d[e.chart_metric]||d[i]}function E(e,t){const n=function(e){return C(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class P{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,r,l,c,d,p,h){const f=t.entities?.power,_=f?c.states[f]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===s||b<0,w=t.entities?.switch,$=w?c.states[w]:null,S=$?"on"===$.state:(_?.attributes?.relay_state||t.relay_state)===a,k=t.breaker_rating_a,E=k?`${Math.round(k)}A`:"",P=m(t.name||n("grid.unknown")),A=C(d);let z;if("current"===A.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;z=`${A.format(i)}A`}else z=`${x(b)}${y(b)}`;const N=u[h||"unknown"]||u.unknown,M=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",q=``;let j="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);j=`${Math.round(e)}%`}const I=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${E?`${E}`:""}\n ${P}\n
\n
\n \n ${z}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${M}\n ${j}\n ${q}\n
\n
\n
\n `}function z(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},M={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},T={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return q(e,N)}function I(e){return q(e,M)}function F(e){return q(e,T)}function R(e){return q(e,L)}function D(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${m(l)}:\n ${m(d)}\n
\n `}return a}function H(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${c}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${c}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${c}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function W(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function B(e){return Math.max(500,Math.floor(e/5e3))}function G(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function V(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function U(e,t,n,o,a,s,r,l){const{options:c,series:p}=function(e,t,n,o,a){n||(n=d[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,p=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),g=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],h={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return p&&(h.min=n.fixedMin,h.max=n.fixedMax),a&&"current"===n.entityRole&&(h.min=0,h.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:h,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=p}function J(e,t,i,o,r){if(!e||!i||!t)return;const l=O(o);let c=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==s&&(c+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=w(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=w(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=w(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),g=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",g&&(g.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=w(e)}else u.textContent="--";g&&(g.textContent="kW")}}const h=e.querySelector(".stat-battery .stat-value");if(h){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(h.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,c);const d=C(o),p="current"===d.entityRole;for(const[o,c]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=c.entities?.power,h=g?t.states[g]:null,m=h&&parseFloat(h.state)||0,f=c.device_type===s||m<0,_=c.entities?.switch,b=_?t.states[_]:null,v=b?"on"===b.state:(h?.attributes?.relay_state||c.relay_state)===a,w=i.querySelector(".power-value");if(w)if(p){const e=c.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${d.format(i)}A`}else w.innerHTML=`${x(m)}${y(m)}`;const $=i.querySelector(".toggle-pill");if($){$.className="toggle-pill "+(v?"toggle-on":"toggle-off");const e=$.querySelector(".toggle-label");e&&(e.textContent=n(v?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!v),i.classList.toggle("circuit-producer",f);const S=c.entities?.select,k=S?t.states[S]:null,C=k?k.state:"unknown",E=u[C]||u.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const A=i.querySelector(".chart-container");if(A){U(A,t,r.get(o)||[],l,d,f,i.classList.contains("circuit-col-span")?200:100,c.breaker_rating_a)}}}function X(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${x(i)} ${y(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=p.power;n.endsWith("_soc")?s=p.soc:n.endsWith("_soe")&&(s=p.soe);const r=!!e.closest(".bess-chart-col");U(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}function K(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===r&&(e.soc=I(i),e.soe=F(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${c}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function Q(e,t,n,i){if(!t||!e)return;const o=O(n),a=[],s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=E(i,n);t&&(a.push(t),s.set(t,e))}if(K(t,a,s),0===a.length)return;o>72e5?await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}(e,a,s,o,i):await async function(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),l=B(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,V(t,r,l))}}}(e,a,s,o,i)}class Y{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new P,this._updateInterval=null,this._hass=null,this._config=null}async render(e,t,i,o){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=o;try{const e=await b(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t);const a=this._topology,s=Math.ceil(this._panelSize/2),c=(O(o),this._monitoringCache.status),d=function(e,t){const i=m(e.device_name||n("header.default_name")),o=m(e.serial||""),a=m(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(a,o),p=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),u=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":k(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=$(Math.max(...n));0===S(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=z(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=z(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(a,s,0,t,o,c),g=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[c,d]of Object.entries(e.sub_devices)){if(d.type===r&&!o)continue;if(d.type===l&&!a)continue;const e=d.type===l?n("subdevice.ev_charger"):d.type===r?n("subdevice.battery"):n("subdevice.fallback"),p=j(d),u=p?t.states[p]:null,g=u&&parseFloat(u.state)||0,h=d.type===r,f=h?I(d):null,_=h?F(d):null,b=h?R(d):null,v=D(d,t,i,new Set([p,f,_,b].filter(Boolean))),w=H(c,0,h,p,f,_);s+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${x(g)} ${y(g)}`:""}\n
\n ${w}\n ${v}\n
\n `}return s}(a,t,o);e.innerHTML=`\n \n ${d}\n ${p}\n ${!1!==o.show_panel?`\n
\n ${u}\n
\n `:""}\n ${g?`
${g}
`:""}\n \n `,this._bindGearClicks(e,a),this._bindToggleClicks(e,a),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate()});try{await Q(t,a,o,this._powerHistory)}catch{}J(e,t,a,o,this._powerHistory),X(e,t,a,o,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),J(e,this._hass,a,this._config,this._powerHistory),X(e,this._hass,a,this._config,this._powerHistory)},1e3)}_recordSamples(){if(!this._topology||!this._hass)return;const e=O(this._config),t=W(e),n=B(e),i=Date.now(),o=i-e;for(const[e,a]of Object.entries(this._topology.circuits)){const s=E(a,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=this._powerHistory.get(e)||[];c.length>0&&i-c[c.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;o.open({...s,uuid:a,monitoringInfo:r})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}}const Z="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",ee="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",te="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",ne="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ie="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function oe(e,t,n,i,o){return`\n ${i}\n `}class ae{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let a;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:o,service:"get_monitoring_status",service_data:e,return_response:!0});a=n?.response||null}catch{a=null}const s=a?.global_settings||{},r=!0===a?.enabled,l=a?.circuits||{},c=a?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=s.notify_targets||"notify.notify",g=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),h=s.notification_title_template||"SPAN: {name} {alert_type}",f=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),C=Object.entries(c).map(([e,t])=>{const i=m(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=m(e);return`\n \n \n \n \n ${oe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${oe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${oe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${oe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=g.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${m(o)} (${m(e)})`:m(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${C}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const E=e.querySelector("#toggle-all-circuits");E&&!$&&S&&(E.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:o,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const a=e.querySelector("#toggle-all-circuits");a&&a.addEventListener("change",async()=>{const s=a.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:o,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:a})})])}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,a=n.checked;try{await t.callWS({type:"call_service",domain:o,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:a})})}catch{return void(n.checked=!a)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const a=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(a)),n.set(a,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const a=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:o,service:l,service_data:this._serviceData({[c]:a,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,a=n.dataset.type,s="mains"===a?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===a?{leg:i}:{circuit_id:i});await t.callService(o,s,r),await this.render(e,t)})}}class se{render(e,t){const i=t?`/config/integrations/integration/span_panel#config_entry=${t}`:"/config/integrations/integration/span_panel";e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n
\n `}}class re extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new Y,this._monitoringTab=new ae,this._settingsTab=new se}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===o)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;this._settingsTab.render(e,n);break}}}}customElements.define("span-panel",re),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown,N=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),I=T?m:"#555",L=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${L}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},I={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},L={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,I)}function j(e){return H(e,L)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d&&(g.min=n.fixedMin,g.max=n.fixedMax),a&&"current"===n.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:h}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f);const k=d.entities?.select,z=k?t.states[k]:null,C=z?z.state:"unknown",E=g[C]||g.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,_,v].filter(Boolean))),y=O(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 8def4dc..8507ba8 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -1,8 +1,9 @@ -import { DEFAULT_CHART_METRIC, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; +import { DEFAULT_CHART_METRIC, DEFAULT_GRAPH_HORIZON, GRAPH_HORIZONS, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; import { t } from "../i18n.js"; import { escapeHtml } from "../helpers/sanitize.js"; -import { getHistoryDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; +import { getHistoryDurationMs, getHorizonDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; +import { GraphSettingsCache } from "../core/graph-settings.js"; import { buildHeaderHTML } from "../core/header-renderer.js"; import { buildGridHTML } from "../core/grid-renderer.js"; import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; @@ -36,6 +37,8 @@ export class SpanPanelCard extends HTMLElement { this._handleUnitToggle = this._onUnitToggle.bind(this); this._handleGearClick = this._onGearClick.bind(this); this._monitoringCache = new MonitoringStatusCache(); + this._graphSettingsCache = new GraphSettingsCache(); + this._horizonMap = new Map(); } connectedCallback() { @@ -59,7 +62,9 @@ export class SpanPanelCard extends HTMLElement { this._rendered = false; this._historyLoaded = false; this._powerHistory.clear(); + this._horizonMap.clear(); this._monitoringCache.clear(); + this._graphSettingsCache.clear(); } get _durationMs() { @@ -145,8 +150,24 @@ export class SpanPanelCard extends HTMLElement { async _loadHistory() { if (this._historyLoaded || !this._topology || !this._hass) return; this._historyLoaded = true; + + // Fetch graph settings and build horizon map + try { + await this._graphSettingsCache.fetch(this._hass); + const settings = this._graphSettingsCache.settings; + if (settings && this._topology?.circuits) { + for (const uuid of Object.keys(this._topology.circuits)) { + const override = settings.circuits?.[uuid]; + const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; + this._horizonMap.set(uuid, horizon); + } + } + } catch { + // Graph settings unavailable — use defaults + } + try { - await loadHistory(this._hass, this._topology, this._config, this._powerHistory); + await loadHistory(this._hass, this._topology, this._config, this._powerHistory, this._horizonMap); this._updateDOM(); } catch (err) { console.warn("SPAN Panel: history fetch failed, charts will populate live", err); @@ -158,21 +179,30 @@ export class SpanPanelCard extends HTMLElement { _recordPowerHistory() { if (!this._topology || !this._hass) return; const now = Date.now(); - const cutoff = now - this._durationMs; - const maxPoints = getMaxHistoryPoints(this._durationMs); for (const [uuid, circuit] of Object.entries(this._topology.circuits)) { + const horizon = this._horizonMap?.get(uuid) || DEFAULT_GRAPH_HORIZON; + if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; + const entityId = getCircuitChartEntity(circuit, this._config); if (!entityId) continue; const state = this._hass.states[entityId]; const rawValue = state ? parseFloat(state.state) || 0 : 0; + + const durationMs = getHorizonDurationMs(horizon); + const maxPoints = getMaxHistoryPoints(durationMs); + const cutoff = now - durationMs; recordSample(this._powerHistory, uuid, rawValue, now, cutoff, maxPoints); } + // Sub-devices always use default duration + const defaultDurationMs = getHistoryDurationMs(this._config); + const defaultMaxPoints = getMaxHistoryPoints(defaultDurationMs); + const defaultCutoff = now - defaultDurationMs; for (const { entityId, key } of collectSubDeviceEntityIds(this._topology)) { const state = this._hass.states[entityId]; const rawValue = state ? parseFloat(state.state) || 0 : 0; - recordSample(this._powerHistory, key, rawValue, now, cutoff, maxPoints); + recordSample(this._powerHistory, key, rawValue, now, defaultCutoff, defaultMaxPoints); } } @@ -186,7 +216,7 @@ export class SpanPanelCard extends HTMLElement { // ── DOM updates (incremental) ────────────────────────────────────────────── _updateDOM() { - updateCircuitDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory); + updateCircuitDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory, this._horizonMap); updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory); } From a60441aa4d8aac41cf04482021a147fa3551d1e8 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:27:32 -0700 Subject: [PATCH 042/101] fix: add missing graph horizon support to standalone card --- dist/span-panel-card.js | 2 +- src/card/span-panel-card.js | 59 ++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 39bd4d2..618e90c 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",s="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,n,i,o,a,l,c,d,p,u){const f=n.entities?.power,_=f?c.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===s,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},F={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function I(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return I(e,L)}function O(e){return I(e,R)}function G(e){return I(e,H)}function W(e){return I(e,F)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${m(l)}:\n ${m(d)}\n
\n `}return a}function B(e,n,i,o,a,s){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!a},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!s},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=O(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):_(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=_(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(V(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,a,s,r,l){const{options:c,series:d}=function(e,t,i,o,a){i||(i=p[n]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d&&(g.min=i.fixedMin,g.max=i.fixedMax),a&&"current"===i.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(i,o,a,s,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,a,l){if(!e||!i||!n)return;const c=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===s,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_);const S=d.entities?.select,k=S?n.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=i.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=i.querySelector(".chart-container");if(N){const e=a.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=t("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const a=document.createElement("div");a.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.graph_horizon"),a.appendChild(s);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,a.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const n=document.createElement("option");n.value=e,n.textContent=t(`horizon.${e}`),e===c&&(n.selected=!0),g.appendChild(n)}u.appendChild(h),u.appendChild(g),p.appendChild(u),a.appendChild(p);const f=d.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=n.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(a)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,n,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${a}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=i.find(e=>e.id===n)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!a.every(Number.isFinite))continue;const s=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(s[e])){r=s[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:a,name:l,voltage:n.attributes.voltage||(2===a.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(s.identifiers)for(const e of s.identifiers)e[0]===a&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),a=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),s={};for(const t of n)s[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":a?"evse":"unknown",entities:s}}return{topology:{serial:h,firmware:s.sw_version||"",panel_size:g,device_id:n,device_name:s.name_by_user||s.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const a=this._horizonMap?.get(t)||i;if(!o[a]?.useRealtime)continue;const s=C(n,this._config);if(!s)continue;const r=this._hass.states[s],l=r&&parseFloat(r.state)||0,c=v(a),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=_(this._config),n=b(t),a=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],s=o&&parseFloat(o.state)||0;y(this._powerHistory,i,s,e,a,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const a=_(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=u.power;n.endsWith("_soc")?s=u.soc:n.endsWith("_soe")&&(s=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const a=o.entities?.switch;if(!a)return;const s=this._hass.states[a];if(!s)return void console.warn("SPAN Panel: switch entity not found:",a);const r="on"===s.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}_onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const i=t.dataset.uuid;if(!i||!this._topology)return;const o=this._topology.circuits[i];if(!o)return;const a=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;n.open({...o,uuid:i,monitoringInfo:a})}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),a=m(e.firmware||""),s="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(n,this._config)),a=this._monitoringCache.status,s=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${a>0?`${a} ${t(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${t(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(a),r=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=T(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=T(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,a),d=function(e,n,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!a)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?O(d):null,_=g?G(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=B(r,0,g,p,f,_);s+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${z(h)} ${E(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${s}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===a)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===a)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.panel_label"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_window"),s.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const a=document.createElement("input");a.type="number",a.min=t,a.max=n,a.value=String(e),a.style.cssText=l;const s=document.createElement("span");return s.textContent=i,s.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(a),o.appendChild(s),{wrap:o,input:a}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const a=document.createElement("div");a.style.cssText=o;const s=document.createElement("label");s.textContent=t("editor.chart_metric"),s.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),a.appendChild(s),a.appendChild(r),e.appendChild(a),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const a=document.createElement("label");a.textContent=t("editor.visible_sections"),a.style.cssText=n,o.appendChild(a);const s=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of s){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let a=null;e.subDeviceType&&(a=document.createElement("div"),a.style.cssText="padding-left: 26px;",a.style.display=n.checked?"block":"none",o.appendChild(a),this._entityContainers[e.subDeviceType]=a),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},a&&(a.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const a=document.createElement("div");a.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const s=document.createElement("input");s.type="checkbox",s.checked=!0===t[i],s.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",a.appendChild(s),a.appendChild(r),e.appendChild(a),s.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};s.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${a}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,c,l,d,p,u){const _=i.entities?.power,m=_?l.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?l.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${z(v)}${E(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,L)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||i;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),c=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===c&&(e.soc=G(n),e.soe=O(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d&&(g.min=n.fixedMin,g.max=n.fixedMax),s&&"current"===n.entityRole&&(g.min=0,g.max=Math.ceil(1.25*s),h.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(n,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,i,n,o,s,c){if(!e||!n||!i)return;const l=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${z(f)}${E(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m);const S=d.entities?.select,k=S?i.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,c?.has(o)?v(c.get(o)):l,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=c?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===l&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",n.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),c=n.filter(e=>e.via_device_id===i),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(n)} ${E(n)}`)}const c=i.querySelectorAll("[data-chart-key]");for(const e of c){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?c.add(n):l.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=e.circuit.entities?.select;return{monInfo:i,sheddingPriority:r&&n.states[r]?n.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,_=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${z(h)} ${E(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=i+"width: 70px; cursor: text;",l=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||n;const l=i.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 8507ba8..e488389 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -31,11 +31,13 @@ export class SpanPanelCard extends HTMLElement { this._historyLoaded = false; this._updateInterval = null; + this._recorderRefreshInterval = null; this._rendered = false; this._handleToggleClick = this._onToggleClick.bind(this); this._handleUnitToggle = this._onUnitToggle.bind(this); this._handleGearClick = this._onGearClick.bind(this); + this._handleGraphSettingsChanged = this._onGraphSettingsChanged.bind(this); this._monitoringCache = new MonitoringStatusCache(); this._graphSettingsCache = new GraphSettingsCache(); this._horizonMap = new Map(); @@ -47,6 +49,26 @@ export class SpanPanelCard extends HTMLElement { this._updateData(); } }, LIVE_SAMPLE_INTERVAL_MS); + + this._recorderRefreshInterval = setInterval(async () => { + if (!this._discovered || !this._hass || !this._topology) return; + const nonRealtimeMap = new Map(); + for (const [uuid, horizon] of this._horizonMap) { + if (!GRAPH_HORIZONS[horizon]?.useRealtime) { + nonRealtimeMap.set(uuid, horizon); + } + } + if (nonRealtimeMap.size === 0) return; + for (const uuid of nonRealtimeMap.keys()) { + this._powerHistory.delete(uuid); + } + try { + await loadHistory(this._hass, this._topology, this._config, this._powerHistory, nonRealtimeMap); + this._updateDOM(); + } catch { + // Will refresh on next interval + } + }, 30000); } disconnectedCallback() { @@ -54,6 +76,10 @@ export class SpanPanelCard extends HTMLElement { clearInterval(this._updateInterval); this._updateInterval = null; } + if (this._recorderRefreshInterval) { + clearInterval(this._recorderRefreshInterval); + this._recorderRefreshInterval = null; + } } setConfig(config) { @@ -270,7 +296,7 @@ export class SpanPanelCard extends HTMLElement { // ── Gear click handler ──────────────────────────────────────────────────── - _onGearClick(event) { + async _onGearClick(event) { const gearBtn = event.target.closest(".gear-icon"); if (!gearBtn) return; @@ -291,13 +317,42 @@ export class SpanPanelCard extends HTMLElement { const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; + await this._graphSettingsCache.fetch(this._hass); + const graphSettings = this._graphSettingsCache.settings; + const graphHorizonInfo = graphSettings?.circuits?.[uuid] + ? graphSettings.circuits[uuid] + : { horizon: graphSettings?.global_horizon || "5m", has_override: false }; + sidePanel.open({ ...circuit, uuid, monitoringInfo, + graphHorizonInfo, }); } + // ── Graph settings changed handler ──────────────────────────────────────── + + async _onGraphSettingsChanged() { + this._graphSettingsCache.invalidate(); + await this._graphSettingsCache.fetch(this._hass); + + // Rebuild horizon map + const settings = this._graphSettingsCache.settings; + if (settings && this._topology?.circuits) { + for (const uuid of Object.keys(this._topology.circuits)) { + const override = settings.circuits?.[uuid]; + const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; + this._horizonMap.set(uuid, horizon); + } + } + + // Reload all history with new horizons + this._powerHistory.clear(); + this._historyLoaded = false; + await this._loadHistory(); + } + // ── Full render ──────────────────────────────────────────────────────────── _render() { @@ -328,6 +383,7 @@ export class SpanPanelCard extends HTMLElement { this.shadowRoot.removeEventListener("click", this._handleToggleClick); this.shadowRoot.removeEventListener("click", this._handleUnitToggle); this.shadowRoot.removeEventListener("click", this._handleGearClick); + this.shadowRoot.removeEventListener("graph-settings-changed", this._handleGraphSettingsChanged); this.shadowRoot.innerHTML = ` @@ -352,6 +408,7 @@ export class SpanPanelCard extends HTMLElement { this.shadowRoot.addEventListener("click", this._handleToggleClick); this.shadowRoot.addEventListener("click", this._handleUnitToggle); this.shadowRoot.addEventListener("click", this._handleGearClick); + this.shadowRoot.addEventListener("graph-settings-changed", this._handleGraphSettingsChanged); const sidePanel = this.shadowRoot.querySelector("span-side-panel"); if (sidePanel) sidePanel.hass = hass; From 8e56d2be2dbe827aa552d7343f33924d536b0254 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:49:12 -0700 Subject: [PATCH 043/101] fix: show decimal precision on Y-axis for sub-watt power values --- src/chart/chart-options.js | 7 +++++++ src/constants.js | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/chart/chart-options.js b/src/chart/chart-options.js index 73c0741..a39242a 100644 --- a/src/chart/chart-options.js +++ b/src/chart/chart-options.js @@ -36,6 +36,9 @@ export function buildChartOptions(history, durationMs, metric, isProducer, break }, ]; + // Determine the max data value to ensure a meaningful Y-axis range + const dataMax = data.length > 0 ? Math.max(...data.map(d => d[1])) : 0; + const yAxis = { type: "value", splitNumber: 4, @@ -45,6 +48,10 @@ export function buildChartOptions(history, durationMs, metric, isProducer, break if (hasFixedRange) { yAxis.min = metric.fixedMin; yAxis.max = metric.fixedMax; + } else if (dataMax < 1) { + // Prevent all-zero Y-axis labels when values are very small + yAxis.min = 0; + yAxis.max = 1; } // When displaying current with a known breaker rating, fix Y-axis to 125% diff --git a/src/constants.js b/src/constants.js index 6e5eccc..16a8570 100644 --- a/src/constants.js +++ b/src/constants.js @@ -37,7 +37,12 @@ export const CHART_METRICS = { entityRole: "power", label: () => t("metric.power"), unit: v => (Math.abs(v) >= 1000 ? "kW" : "W"), - format: v => (Math.abs(v) >= 1000 ? (Math.abs(v) / 1000).toFixed(1) : String(Math.round(Math.abs(v)))), + format: v => { + const abs = Math.abs(v); + if (abs >= 1000) return (abs / 1000).toFixed(1); + if (abs < 10 && abs > 0) return abs.toFixed(1); + return String(Math.round(abs)); + }, }, current: { entityRole: "current", From 808a20ec2fafcc03b07dbb6073431459d435de7c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:52:46 -0700 Subject: [PATCH 044/101] fix: use decimal Y-axis labels for small values on both power and current --- src/chart/chart-options.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/chart/chart-options.js b/src/chart/chart-options.js index a39242a..8ae1a82 100644 --- a/src/chart/chart-options.js +++ b/src/chart/chart-options.js @@ -38,18 +38,21 @@ export function buildChartOptions(history, durationMs, metric, isProducer, break // Determine the max data value to ensure a meaningful Y-axis range const dataMax = data.length > 0 ? Math.max(...data.map(d => d[1])) : 0; + const useDecimalAxis = dataMax < 10; const yAxis = { type: "value", splitNumber: 4, - axisLabel: { fontSize: 10, formatter: v => metric.format(v) }, + axisLabel: { + fontSize: 10, + formatter: useDecimalAxis ? v => (v === 0 ? "0" : v.toFixed(1)) : v => metric.format(v), + }, splitLine: { lineStyle: { opacity: 0.15 } }, }; if (hasFixedRange) { yAxis.min = metric.fixedMin; yAxis.max = metric.fixedMax; } else if (dataMax < 1) { - // Prevent all-zero Y-axis labels when values are very small yAxis.min = 0; yAxis.max = 1; } From 8e825664e7e2a0e6affeb1212d5ffbf412e5c7d9 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:06:52 -0700 Subject: [PATCH 045/101] fix: convert statistics timestamps from seconds to milliseconds --- src/core/history-loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/history-loader.js b/src/core/history-loader.js index 68c3d0d..d7fb1c6 100644 --- a/src/core/history-loader.js +++ b/src/core/history-loader.js @@ -24,7 +24,8 @@ async function loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, for (const entry of stats) { const val = entry.mean; if (val == null || !Number.isFinite(val)) continue; - const time = entry.start; + // HA statistics returns start as epoch seconds; convert to milliseconds + const time = entry.start * 1000; if (time > 0) hist.push({ time, value: val }); } From 73d6a3fd3fb373d3fe983febd4973480dc6c69c7 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:08:50 -0700 Subject: [PATCH 046/101] feat: add 1-week graph time horizon scale --- src/constants.js | 1 + src/i18n.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/constants.js b/src/constants.js index 16a8570..240468a 100644 --- a/src/constants.js +++ b/src/constants.js @@ -18,6 +18,7 @@ export const GRAPH_HORIZONS = { "5m": { ms: 5 * 60 * 1000, refreshMs: 1000, useRealtime: true }, "1h": { ms: 60 * 60 * 1000, refreshMs: 30000, useRealtime: false }, "1d": { ms: 24 * 60 * 60 * 1000, refreshMs: 60000, useRealtime: false }, + "1w": { ms: 7 * 24 * 60 * 60 * 1000, refreshMs: 60000, useRealtime: false }, "1M": { ms: 30 * 24 * 60 * 60 * 1000, refreshMs: 60000, useRealtime: false }, }; diff --git a/src/i18n.js b/src/i18n.js index be3e716..a68cf8c 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -70,6 +70,7 @@ const translations = { "horizon.5m": "5 Minutes", "horizon.1h": "1 Hour", "horizon.1d": "1 Day", + "horizon.1w": "1 Week", "horizon.1M": "1 Month", "settings.graph_horizon_heading": "Graph Time Horizon", "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", @@ -224,6 +225,7 @@ const translations = { "horizon.5m": "5 Minutes", "horizon.1h": "1 Hour", "horizon.1d": "1 Day", + "horizon.1w": "1 Week", "horizon.1M": "1 Month", "settings.graph_horizon_heading": "Graph Time Horizon", "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", @@ -363,6 +365,7 @@ const translations = { "horizon.5m": "5 Minutes", "horizon.1h": "1 Hour", "horizon.1d": "1 Day", + "horizon.1w": "1 Week", "horizon.1M": "1 Month", "settings.graph_horizon_heading": "Graph Time Horizon", "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", @@ -504,6 +507,7 @@ const translations = { "horizon.5m": "5 Minutes", "horizon.1h": "1 Hour", "horizon.1d": "1 Day", + "horizon.1w": "1 Week", "horizon.1M": "1 Month", "settings.graph_horizon_heading": "Graph Time Horizon", "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", @@ -647,6 +651,7 @@ const translations = { "horizon.5m": "5 Minutes", "horizon.1h": "1 Hour", "horizon.1d": "1 Day", + "horizon.1w": "1 Week", "horizon.1M": "1 Month", "settings.graph_horizon_heading": "Graph Time Horizon", "settings.graph_horizon_description": "Default time window for all circuit graphs. Individual circuits can override this in their settings panel.", From c3be98d0f9fda8a63b695880be83398aaa1f499a Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:18:43 -0700 Subject: [PATCH 047/101] chore: rebuild dist with timestamp fix, decimal axis, and 1-week scale --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 618e90c..1253c5d 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,c,l,d,p,u){const _=i.entities?.power,m=_?l.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?l.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${z(v)}${E(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,L)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||i;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),c=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===c&&(e.soc=G(n),e.soe=O(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d&&(g.min=n.fixedMin,g.max=n.fixedMax),s&&"current"===n.entityRole&&(g.min=0,g.max=Math.ceil(1.25*s),h.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:h}}(n,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,i,n,o,s,c){if(!e||!n||!i)return;const l=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${z(f)}${E(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m);const S=d.entities?.select,k=S?i.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,c?.has(o)?v(c.get(o)):l,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=c?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===l&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",n.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),c=n.filter(e=>e.via_device_id===i),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(n)} ${E(n)}`)}const c=i.querySelectorAll("[data-chart-key]");for(const e of c){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?c.add(n):l.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=e.circuit.entities?.select;return{monInfo:i,sheddingPriority:r&&n.states[r]?n.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,_=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${z(h)} ${E(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=i+"width: 70px; cursor: text;",l=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||n;const l=i.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,c,l,d,p,u){const _=i.entities?.power,m=_?l.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?l.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,L)}function G(e){return F(e,R)}function W(e){return F(e,H)}function O(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||i;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=1e3*e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),c=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===c&&(e.soc=G(n),e.soe=W(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),s&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,i,n,o,s,c){if(!e||!n||!i)return;const l=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${E(f)}${z(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m);const S=d.entities?.select,k=S?i.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,c?.has(o)?v(c.get(o)):l,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=c?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===l&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",n.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),c=n.filter(e=>e.via_device_id===i),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(n)} ${z(n)}`)}const c=i.querySelectorAll("[data-chart-key]");for(const e of c){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?c.add(n):l.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=e.circuit.entities?.select;return{monInfo:i,sheddingPriority:r&&n.states[r]?n.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,_=g?G(d):null,m=g?W(d):null,v=g?O(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=i+"width: 70px; cursor: text;",l=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||n;const l=i.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 9991f7e..feeff18 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>Math.abs(e)>=1e3?(Math.abs(e)/1e3).toFixed(1):String(Math.round(Math.abs(e)))},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown,N=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),I=T?m:"#555",L=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${L}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},I={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},L={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,I)}function j(e){return H(e,L)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=[{type:"line",data:(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],g={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d&&(g.min=n.fixedMin,g.max=n.fixedMax),a&&"current"===n.entityRole&&(g.min=0,g.max=Math.ceil(1.25*a),h.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),h.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:g,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:h}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f);const k=d.entities?.select,z=k?t.states[k]:null,C=z?z.state:"unknown",E=g[C]||g.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,_,v].filter(Boolean))),y=O(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown,N=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),I=T?m:"#555",L=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${L}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},I={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},L={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,I)}function j(e){return H(e,L)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f);const k=d.entities?.select,z=k?t.states[k]:null,C=z?z.state:"unknown",E=g[C]||g.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=1e3*e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,_,v].filter(Boolean))),y=W(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); From 23f251bd93504d8cc95579914b161b072829736c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Tue, 31 Mar 2026 02:07:17 -0700 Subject: [PATCH 048/101] fix: remove double millisecond conversion on statistics timestamps HA's recorder/statistics_during_period WS API already converts start timestamps from epoch seconds to epoch milliseconds before sending. The card was multiplying by 1000 again, pushing all statistics data points ~31k years into the future and off the visible chart range. This caused 1d, 1w, and 1M horizons to show no data. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/core/history-loader.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 1253c5d..9ab9ea9 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,c,l,d,p,u){const _=i.entities?.power,m=_?l.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?l.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,L)}function G(e){return F(e,R)}function W(e){return F(e,H)}function O(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||i;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=1e3*e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),c=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===c&&(e.soc=G(n),e.soe=W(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),s&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,i,n,o,s,c){if(!e||!n||!i)return;const l=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${E(f)}${z(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m);const S=d.entities?.select,k=S?i.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,c?.has(o)?v(c.get(o)):l,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=c?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===l&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",n.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),c=n.filter(e=>e.via_device_id===i),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(n)} ${z(n)}`)}const c=i.querySelectorAll("[data-chart-key]");for(const e of c){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?c.add(n):l.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=e.circuit.entities?.select;return{monInfo:i,sheddingPriority:r&&n.states[r]?n.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,_=g?G(d):null,m=g?W(d):null,v=g?O(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=i+"width: 70px; cursor: text;",l=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||n;const l=i.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,c,l,d,p,u){const _=i.entities?.power,m=_?l.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?l.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,L)}function G(e){return F(e,R)}function W(e){return F(e,H)}function O(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||i;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),c=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===c&&(e.soc=G(n),e.soe=W(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),s&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,i,n,o,s,c){if(!e||!n||!i)return;const l=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${E(f)}${z(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m);const S=d.entities?.select,k=S?i.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,c?.has(o)?v(c.get(o)):l,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=c?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===l&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",n.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),c=n.filter(e=>e.via_device_id===i),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(n)} ${z(n)}`)}const c=i.querySelectorAll("[data-chart-key]");for(const e of c){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?c.add(n):l.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=e.circuit.entities?.select;return{monInfo:i,sheddingPriority:r&&n.states[r]?n.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,_=g?G(d):null,m=g?W(d):null,v=g?O(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=i+"width: 70px; cursor: text;",l=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||n;const l=i.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index feeff18..73c557b 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown,N=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),I=T?m:"#555",L=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${L}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},I={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},L={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,I)}function j(e){return H(e,L)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f);const k=d.entities?.select,z=k?t.states[k]:null,C=z?z.state:"unknown",E=g[C]||g.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=1e3*e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,_,v].filter(Boolean))),y=W(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown,N=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),I=T?m:"#555",L=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${L}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},I={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},L={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,I)}function j(e){return H(e,L)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f);const k=d.entities?.select,z=k?t.states[k]:null,C=z?z.state:"unknown",E=g[C]||g.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,_,v].filter(Boolean))),y=W(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/core/history-loader.js b/src/core/history-loader.js index d7fb1c6..f5e3302 100644 --- a/src/core/history-loader.js +++ b/src/core/history-loader.js @@ -24,8 +24,8 @@ async function loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, for (const entry of stats) { const val = entry.mean; if (val == null || !Number.isFinite(val)) continue; - // HA statistics returns start as epoch seconds; convert to milliseconds - const time = entry.start * 1000; + // HA statistics WS API returns start as epoch milliseconds + const time = entry.start; if (time > 0) hist.push({ time, value: val }); } From 9df4e0389a5a20e08bf80326ff515d87c741c924 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:15:28 -0700 Subject: [PATCH 049/101] feat: add always-on and SoC composite shedding priority icons --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 11 +++++++++++ src/constants.js | 3 ++- src/core/dom-updater.js | 33 ++++++++++++++++++++++++++++++--- src/core/grid-renderer.js | 31 ++++++++++++++++++++++++------- src/i18n.js | 5 +++++ 7 files changed, 74 insertions(+), 13 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 9ab9ea9..8234281 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,c,l,d,p,u){const _=i.entities?.power,m=_?l.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?l.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown,A=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=T?g:"#555",L=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${L}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,L)}function G(e){return F(e,R)}function W(e){return F(e,H)}function O(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||i;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),c=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===c&&(e.soc=G(n),e.soe=W(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),s&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,i,n,o,s,c){if(!e||!n||!i)return;const l=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${E(f)}${z(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m);const S=d.entities?.select,k=S?i.states[S]:null,$=k?k.state:"unknown",P=h[$]||h.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",P.icon),M.style.color=P.color,M.title=P.label());const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,c?.has(o)?v(c.get(o)):l,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=c?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===l&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",n.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),c=n.filter(e=>e.via_device_id===i),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(n)} ${z(n)}`)}const c=i.querySelectorAll("[data-chart-key]");for(const e of c){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?c.add(n):l.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;const r=e.circuit.entities?.select;return{monInfo:i,sheddingPriority:r&&n.states[r]?n.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,_=g?G(d):null,m=g?W(d):null,v=g?O(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=i+"width: 70px; cursor: text;",l=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||n;const l=i.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,l,c,d,p,u){const _=i.entities?.power,m=_?c.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?c.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||i;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),l=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===l&&(e.soc=G(n),e.soe=O(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,l){const{options:c,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),s&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,i,n,o,s,l){if(!e||!n||!i)return;const c=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${E(f)}${z(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?i.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=n.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=n.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=n.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===c&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",n.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),l=n.filter(e=>e.via_device_id===i),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of l){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(n)} ${z(n)}`)}const l=i.querySelectorAll("[data-chart-key]");for(const e of l){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?l.add(n):c.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,_=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=i+"width: 70px; cursor: text;",c=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),_=c(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||n;const c=i.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 73c557b..85cb931 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold")},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown,N=``,T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),I=T?m:"#555",L=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${L}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},I={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},L={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,I)}function j(e){return H(e,L)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f);const k=d.entities?.select,z=k?t.states[k]:null,C=z?z.state:"unknown",E=g[C]||g.unknown,P=i.querySelector(".shedding-icon");P&&(P.setAttribute("icon",E.icon),P.style.color=E.color,P.title=E.label());const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;const r=e.circuit.entities?.select;return{monInfo:n,sheddingPriority:r&&i.states[r]?i.states[r].state:"unknown"}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,_,v].filter(Boolean))),y=W(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,L)}function j(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,_,b].filter(Boolean))),y=W(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index 407324d..eef5c5e 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -236,6 +236,17 @@ export const CARD_STYLES = ` padding: 0 4px; } .shedding-icon { opacity: 0.8; cursor: default; } + .shedding-composite { + display: inline-flex; + align-items: center; + gap: 2px; + } + .shedding-icon-secondary { opacity: 0.8; } + .shedding-label { + font-size: 10px; + font-weight: 600; + opacity: 0.8; + } .gear-icon { background: none; border: none; diff --git a/src/constants.js b/src/constants.js index 240468a..bccb358 100644 --- a/src/constants.js +++ b/src/constants.js @@ -72,8 +72,9 @@ export const BESS_CHART_METRICS = { // ── Shedding priority ────────────────────────────────────────────────────── export const SHEDDING_PRIORITIES = { + always_on: { icon: "mdi:battery", icon2: "mdi:router-wireless", color: "#4caf50", label: () => t("shedding.always_on") }, never: { icon: "mdi:shield-check", color: "#4caf50", label: () => t("shedding.never") }, - soc_threshold: { icon: "mdi:battery-alert-variant-outline", color: "#9c27b0", label: () => t("shedding.soc_threshold") }, + soc_threshold: { icon: "mdi:battery-alert-variant-outline", color: "#9c27b0", label: () => t("shedding.soc_threshold"), textLabel: "SoC" }, off_grid: { icon: "mdi:transmission-tower", color: "#ff9800", label: () => t("shedding.off_grid") }, unknown: { icon: "mdi:help-circle-outline", color: "#888", label: () => t("shedding.unknown") }, }; diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index 0b8b8ed..4464c57 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -161,9 +161,14 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor slot.classList.toggle("circuit-producer", isProducer); // Update shedding priority icon - const selectEid = circuit.entities?.select; - const selectState = selectEid ? hass.states[selectEid] : null; - const priority = selectState ? selectState.state : "unknown"; + let priority; + if (circuit.always_on) { + priority = "always_on"; + } else { + const selectEid = circuit.entities?.select; + const selectState = selectEid ? hass.states[selectEid] : null; + priority = selectState ? selectState.state : "unknown"; + } const shedInfo = SHEDDING_PRIORITIES[priority] || SHEDDING_PRIORITIES.unknown; const sheddingIcon = slot.querySelector(".shedding-icon"); if (sheddingIcon) { @@ -171,6 +176,28 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor sheddingIcon.style.color = shedInfo.color; sheddingIcon.title = shedInfo.label(); } + // Update secondary icon if present + const secondaryIcon = slot.querySelector(".shedding-icon-secondary"); + if (secondaryIcon) { + if (shedInfo.icon2) { + secondaryIcon.setAttribute("icon", shedInfo.icon2); + secondaryIcon.style.color = shedInfo.color; + secondaryIcon.style.display = ""; + } else { + secondaryIcon.style.display = "none"; + } + } + // Update text label if present + const sheddingLabel = slot.querySelector(".shedding-label"); + if (sheddingLabel) { + if (shedInfo.textLabel) { + sheddingLabel.textContent = shedInfo.textLabel; + sheddingLabel.style.color = shedInfo.color; + sheddingLabel.style.display = ""; + } else { + sheddingLabel.style.display = "none"; + } + } const chartContainer = slot.querySelector(".chart-container"); if (chartContainer) { diff --git a/src/core/grid-renderer.js b/src/core/grid-renderer.js index 6d2f32d..761512b 100644 --- a/src/core/grid-renderer.js +++ b/src/core/grid-renderer.js @@ -46,8 +46,13 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config, mon function lookupMonitoring(entry) { const circuitEntityId = entry.circuit.entities?.current || entry.circuit.entities?.power; const monInfo = monitoringStatus ? getCircuitMonitoringInfo(monitoringStatus, circuitEntityId) : null; - const selectEid = entry.circuit.entities?.select; - const sheddingPriority = selectEid && hass.states[selectEid] ? hass.states[selectEid].state : "unknown"; + let sheddingPriority; + if (entry.circuit.always_on) { + sheddingPriority = "always_on"; + } else { + const selectEid = entry.circuit.entities?.select; + sheddingPriority = selectEid && hass.states[selectEid] ? hass.states[selectEid].state : "unknown"; + } return { monInfo, sheddingPriority }; } @@ -129,13 +134,25 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, valueHTML = `${formatPowerSigned(powerW)}${formatPowerUnit(powerW)}`; } - // Shedding icon + // Shedding icon (supports composite: dual-icon or icon+text) const priority = sheddingPriority || "unknown"; const shedInfo = SHEDDING_PRIORITIES[priority] || SHEDDING_PRIORITIES.unknown; - const sheddingHTML = ``; + let sheddingHTML; + if (shedInfo.icon2) { + sheddingHTML = ` + + + `; + } else if (shedInfo.textLabel) { + sheddingHTML = ` + + ${shedInfo.textLabel} + `; + } else { + sheddingHTML = ``; + } // Gear icon const hasOverridesFlag = monitoringInfo && hasCustomOverrides(monitoringInfo); diff --git a/src/i18n.js b/src/i18n.js index a68cf8c..e1cbc6b 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -169,6 +169,7 @@ const translations = { "metric.current": "Current", "metric.soc": "State of Charge", "metric.soe": "State of Energy", + "shedding.always_on": "Always On", "shedding.never": "Never", "shedding.soc_threshold": "SoC Threshold", "shedding.off_grid": "Off-Grid", @@ -309,6 +310,7 @@ const translations = { "metric.current": "Corriente", "metric.soc": "Estado de Carga", "metric.soe": "Estado de Energ\u00eda", + "shedding.always_on": "Siempre Encendido", "shedding.never": "Nunca", "shedding.soc_threshold": "Umbral SoC", "shedding.off_grid": "Fuera de Red", @@ -451,6 +453,7 @@ const translations = { "metric.current": "Courant", "metric.soc": "\u00c9tat de Charge", "metric.soe": "\u00c9tat d'\u00c9nergie", + "shedding.always_on": "Toujours Actif", "shedding.never": "Jamais", "shedding.soc_threshold": "Seuil SoC", "shedding.off_grid": "Hors R\u00e9seau", @@ -595,6 +598,7 @@ const translations = { "metric.current": "\u96fb\u6d41", "metric.soc": "\u5145\u96fb\u72b6\u614b", "metric.soe": "\u30a8\u30cd\u30eb\u30ae\u30fc\u72b6\u614b", + "shedding.always_on": "\u5e38\u6642\u30aa\u30f3", "shedding.never": "\u306a\u3057", "shedding.soc_threshold": "SoC\u3057\u304d\u3044\u5024", "shedding.off_grid": "\u30aa\u30d5\u30b0\u30ea\u30c3\u30c9", @@ -736,6 +740,7 @@ const translations = { "metric.current": "Corrente", "metric.soc": "Estado de Carga", "metric.soe": "Estado de Energia", + "shedding.always_on": "Sempre Ligado", "shedding.never": "Nunca", "shedding.soc_threshold": "Limite SoC", "shedding.off_grid": "Fora da Rede", From def81efb65305b59cbc25052e0784a2ae7a8321c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:03:43 -0700 Subject: [PATCH 050/101] feat: replace graph time horizon radio buttons with segmented button bar Replace Global/Custom radio + dropdown with a single segmented bar showing all horizon options. Active selection updates the graph immediately. When Global is selected, the resolved horizon gets a secondary underline indicator. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/span-panel-card.js | 5 +- src/core/side-panel.js | 131 +++++++++++++++++++++--------------- src/panel/tab-dashboard.js | 5 +- 5 files changed, 86 insertions(+), 59 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 8234281..c761192 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const i="power",n="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,i=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(i,6e4)}function v(e){const t=o[e];return t?t.ms:o[n].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,i,n,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:n,value:i});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,i=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const n=[e[0]];for(let t=1;t=i&&n.push(e[t]);return n.length>t&&n.splice(0,n.length-t),n}function x(e){return p[e.chart_metric]||p[i]}function C(e,t){const i=function(e){return x(e).entityRole}(t);return e.entities?.[i]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:n,return_response:!0});this._settings=o?.response||null,this._lastFetch=i}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,i]=[Math.min(...e),Math.max(...e)];return P(t)===P(i)?"row-span":M(t)===M(i)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=i?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,i,n,o,s,l,c,d,p,u){const _=i.entities?.power,m=_?c.states[_]:null,v=m&&parseFloat(m.state)||0,b=i.device_type===r||v<0,y=i.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||i.relay_state)===a,S=i.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=f(i.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=i.entities?.current,t=e?c.states[e]:null,n=t&&parseFloat(t.state)||0;M=`${P.format(n)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==i.is_user_controllable&&i.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[i,n]of Object.entries(e.entities)){if("sensor"!==n.domain)continue;const e=(n.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return i;if(n.unique_id&&t.suffixes.some(e=>n.unique_id.endsWith(e)))return i}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,i,n){const o=i.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[i,a]of Object.entries(e.entities)){if(n.has(i))continue;if(!0!==o[i])continue;const r=t.states[i];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||i;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return s}function B(e,i,n,o,s,a){if(n){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=n/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const i=e.start;i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(n,t)}}}async function U(e,t,i,n,o){const s=new Date(Date.now()-n).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(n),l=function(e){return Math.max(500,Math.floor(e/5e3))}(n);for(const[e,t]of Object.entries(a)){const n=i.get(e);if(!n||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const i=1e3*(e.lu||e.lc||0);i>0&&s.push({time:i,value:t})}if(s.length>0){const e=o.get(n)||[],t=[...s,...e];o.set(n,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[i,n]of Object.entries(e.sub_devices)){const e={power:j(n)};n.type===l&&(e.soc=G(n),e.soe=O(n));for(const[n,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${i}_${n}`})}return t}async function K(e,t,i,n,o){if(!t||!e)return;const s=new Map;for(const[e,n]of Object.entries(t.circuits)){const t=C(n,i);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(i),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(i);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,i){for(const{entityId:n,key:o}of J(e))t.push(n),i.set(n,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,i]of s){if(0===i.entityIds.length)continue;t>72e5?r.push(V(e,i.entityIds,i.uuidByEntity,t,n)):r.push(U(e,i.entityIds,i.uuidByEntity,t,n))}await Promise.all(r)}function X(e,t,n,o,s,a,r,l){const{options:c,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,u=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),s&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(n,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,i,n,o,s,l){if(!e||!n||!i)return;const c=m(o);let d=0;for(const[,e]of Object.entries(n.circuits)){const t=e.entities?.power;if(!t)continue;const n=i.states[t],o=n&&parseFloat(n.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,i,n,o){const s="current"===(n.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=i.panel_entities?.site_power,n=e?t.states[e]:null,o=n?parseFloat(n.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=i.panel_entities?.site_power;if(e){const i=t.states[e];i&&(o=Math.abs(parseFloat(i.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=i.panel_entities?.current_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=i.panel_entities?.feedthrough_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=n?Math.abs(parseFloat(n.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=i.panel_entities?.pv_power,n=e?t.states[e]:null;if(s){const e=n?parseFloat(n.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(n){const e=Math.abs(parseFloat(n.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=i.panel_entities?.battery_level,n=e?t.states[e]:null;n&&(g.textContent=`${Math.round(parseFloat(n.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=i.panel_entities?.dsm_state,n=e?t.states[e]:null;_.textContent=n?t.formatEntityState?.(n)||n.state:"--"}}(e,i,n,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${o}"]`);if(!n)continue;const g=d.entities?.power,_=g?i.states[g]:null,f=_&&parseFloat(_.state)||0,m=d.device_type===r||f<0,b=d.entities?.switch,y=b?i.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=n.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?i.states[e]:null,n=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(n)}A`}else x.innerHTML=`${E(f)}${z(f)}`;const C=n.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(n.classList.toggle("circuit-off",!w),n.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?i.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=n.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=n.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=n.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=n.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=n.classList.contains("circuit-col-span")?200:100;X(N,i,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const i of Object.values(e||{}))for(const e of i.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const i=document.createElement("style");i.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(i);const n=document.createElement("div");n.className="backdrop",n.addEventListener("click",()=>this.close()),t.appendChild(n);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const i=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(i);const n=document.createElement("div");n.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,n.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),n.appendChild(s),e.appendChild(n)}_renderCircuitMode(e,t){const i=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,n=this._createHeader(f(t.name),i);e.appendChild(n);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const i=document.createElement("div");i.className="panel-header";const n=document.createElement("div");n.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),i.appendChild(n),i.appendChild(o),i}_renderRelaySection(e,i){if(!1===i.is_user_controllable||!i.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=i.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderSheddingSection(e,i){if(!i.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=i.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),n.appendChild(o),e.appendChild(n)}_renderGraphHorizonSection(e,i){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=i.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||n,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,s.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=t("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(o)){const i=document.createElement("option");i.value=e,i.textContent=t(`horizon.${e}`),e===c&&(i.selected=!0),g.appendChild(i)}u.appendChild(h),u.appendChild(g),p.appendChild(u),s.appendChild(p);const _=d.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=i.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=i.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(s)}_renderMonitoringSection(e,i){const n=document.createElement("div");n.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=i.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),n.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",n.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,i)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,i)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",i)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",i)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const n=i.entities?.power||i.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(u.style.display=n?"block":"none",!n&&e.checked){const e=i.entities?.power||i.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,i,n,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(n),r.dataset.role=`threshold-${i}`,r.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,i,n,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(n),u.dataset.role=`threshold-${i}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${i}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),i=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),n=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:n?Number(n.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const i=this._hass?.states?.[e.entities.switch]?.state;"on"===i?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const i=this._hass?.states?.[e.entities.select]?.state||"";t.value=i}}}_callService(e,t,i){return this._hass?Promise.resolve(this._hass.callService(e,t,i)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,i){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],i()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,i]of this._horizonMap)o[i]?.useRealtime||e.set(t,i);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,i){const n=await e.callWS({type:`${s}/panel_topology`,device_id:i}),o=n.panel_size||Y(n.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:n,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===i)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,i){const[n,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=n.find(e=>e.id===i)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===i),l=n.filter(e=>e.via_device_id===i),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const i=e.states[t.entity_id];if(!i||!i.attributes||!i.attributes.tabs)continue;const n=i.attributes.tabs;if(!n||!n.startsWith("tabs ["))continue;const o=n.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=i.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:i.attributes.voltage||(2===s.length?240:120),device_type:i.attributes.device_type||"circuit",relay_state:i.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const i=e.states[t.entity_id];if(i&&i.attributes&&i.attributes.panel_size){g=i.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of l){const i=o.filter(e=>e.device_id===t.id),n=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of i)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:n?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:i,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,i]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||n;if(!o[s]?.useRealtime)continue;const a=C(i,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),i=b(t),s=e-t;for(const{entityId:t,key:n}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,n,a,e,s,i)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,i,n,o){if(!i.sub_devices)return;const s=m(n);for(const[n,a]of Object.entries(i.sub_devices)){const i=e.querySelector(`[data-subdev="${n}"]`);if(!i)continue;const r=j(a);if(r){const e=t.states[r],n=e&&parseFloat(e.state)||0,o=i.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(n)} ${z(n)}`)}const l=i.querySelectorAll("[data-chart-key]");for(const e of l){const i=e.dataset.chartKey,n=o.get(i)||[];let a=u.power;i.endsWith("_soc")?a=u.soc:i.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,n,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const n=i.querySelector(`[data-eid="${e}"]`);if(!n)continue;const o=t.states[e];o&&(n.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const i=t.dataset.unit;i&&i!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:i},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const n=i.dataset.uuid,o=this._topology.circuits[n];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const i=this.shadowRoot.querySelector("span-side-panel");if(!i)return;if(i.hass=this._hass,t.classList.contains("panel-gear"))return void i.open({panelMode:!0});const n=t.dataset.uuid;if(!n||!this._topology)return;const o=this._topology.circuits[n];if(!o)return;const s=this._monitoringCache?.status?.circuits?.[o.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const a=this._graphSettingsCache.settings,r=a?.circuits?.[n]?a.circuits[n]:{horizon:a?.global_horizon||"5m",has_override:!1};i.open({...o,uuid:n,monitoringInfo:s,graphHorizonInfo:r})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const i=e.circuits?.[t],o=i?.has_override?i.horizon:e.global_horizon||n;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const i=this._topology,n=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,i){const n=f(e.device_name||t("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(i.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${n}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(i,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const i=Object.values(e.circuits||{}),n=Object.values(e.mains||{}),o=[...i,...n],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${i.length} ${t("status.circuits")} · ${n.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,i,n,o,s){const a=new Map,r=new Set;for(const[t,i]of Object.entries(e.circuits)){const e=i.tabs;if(!e||0===e.length)continue;const n=Math.min(...e),o=1===e.length?"single":N(e);a.set(n,{uuid:t,circuit:i,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const i=t.circuit.tabs,n=P(Math.max(...i));0===M(e)?l.add(n):c.add(n)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,i=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,i=2*e,s=a.get(t),u=a.get(i);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,n,o,t,a),p+=`
${i}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:i}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,n,o,t,i)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(i)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:i}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,n,o,t,i)}p+=`
${i}
`}return p}(i,n,0,e,this._config,s),d=function(e,i,n){const o=!1!==n.show_battery,s=!1!==n.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?i.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,_=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,i,n,new Set([p,_,m,v].filter(Boolean))),y=B(r,0,g,p,_,m);a+=`\n
\n
\n ${f(e)}\n ${f(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(i,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const i=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",n=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${n} (${i})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",i="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",n="margin-bottom: 16px;";this._buildPanelSelector(e,t,i,n),this._buildTimeWindow(e,t,i,n),this._buildMetricSelector(e,t,i,n),this._buildSectionCheckboxes(e,i,n),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=n;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=i+"width: 70px; cursor: text;",c=(e,t,i,n)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=i,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=n,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),_=c(u,"0","59",t("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,i,n,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=i,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,i,n){const o=document.createElement("div");o.style.cssText=n;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=i,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const i=document.createElement("input");i.type="checkbox",i.checked=!1!==this._config[e.key],i.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const n=document.createElement("span");n.textContent=e.label,n.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(i),t.appendChild(n),o.appendChild(t),this._checkboxes[e.key]=i;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=i.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),i.addEventListener("change",()=>{this._config={...this._config,[e.key]:i.checked},s&&(s.style.display=i.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,i){const n=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===n||"battery power"===n||o.endsWith("_power"))return!0;if("bess"===i){if("battery level"===n||"battery percentage"===n||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===n||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===n||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,i]of Object.entries(e)){const e=this._entityContainers[i.type];if(e&&(e.innerHTML="",i.entities))for(const[n,o]of Object.entries(i.entities)){if("sensor"===o.domain&&this._isChartEntity(n,o,i.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[n],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||n;const c=i.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[n]=!0:delete e[n],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),i=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))i.add(t);this._availableRoles=i,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[i,n]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(n.entityRole))continue;const o=document.createElement("option");o.value=i,o.textContent=n.label(),i===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function B(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(V(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=B(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 85cb931..e520985 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,i.appendChild(d);const p=document.createElement("div");p.dataset.role="graph-horizon-fields",p.style.display=l?"block":"none";const u=document.createElement("div");u.className="field-row";const h=document.createElement("span");h.className="field-label",h.textContent=n("settings.default_scale");const g=document.createElement("select");g.dataset.role="graph-horizon-select";for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`),e===c&&(t.selected=!0),g.appendChild(t)}u.appendChild(h),u.appendChild(g),p.appendChild(u),i.appendChild(p);const m=d.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.uuid;this._callDomainService("clear_circuit_graph_horizon",{circuit_id:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))}});g.addEventListener("change",()=>{this._debounce("graph-horizon",500,()=>{const e=t.uuid;this._callDomainService("set_circuit_graph_horizon",{circuit_id:e,horizon:g.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`))})}),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,_=h?c.states[h]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=f(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,L)}function j(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(l)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===l||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${w(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,f,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),_=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,_=g?j(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,_,b].filter(Boolean))),y=W(r,0,g,p,m,_);s+=`\n
\n
\n ${f(e)}\n ${f(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${_?`
${_}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const o=e.querySelector("span-side-panel");if(!o||!this._hass)return;if(o.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const a=i.dataset.uuid;if(!a||!t)return;const s=t.circuits[a];if(!s)return;await this._monitoringCache.fetch(this._hass);const r=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const l=this._graphSettingsCache.settings,c=l?.circuits?.[a]?l.circuits[a]:{horizon:l?.global_horizon||"5m",has_override:!1};o.open({...s,uuid:a,monitoringInfo:r,graphHorizonInfo:c})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,L)}function j(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),f=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${f?`
${f}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index e488389..17e2c06 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -319,9 +319,10 @@ export class SpanPanelCard extends HTMLElement { await this._graphSettingsCache.fetch(this._hass); const graphSettings = this._graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; const graphHorizonInfo = graphSettings?.circuits?.[uuid] - ? graphSettings.circuits[uuid] - : { horizon: graphSettings?.global_horizon || "5m", has_override: false }; + ? { ...graphSettings.circuits[uuid], globalHorizon } + : { horizon: globalHorizon, has_override: false, globalHorizon }; sidePanel.open({ ...circuit, diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 475437b..d27c33a 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -145,6 +145,42 @@ const STYLES = ` cursor: pointer; } + .horizon-bar { + display: flex; + border: 1px solid var(--divider-color, #e0e0e0); + border-radius: 6px; + overflow: hidden; + margin-top: 4px; + } + .horizon-segment { + flex: 1; + padding: 6px 0; + text-align: center; + font-size: 13px; + cursor: pointer; + background: var(--card-background-color, #fff); + color: var(--primary-text-color, #212121); + border: none; + border-right: 1px solid var(--divider-color, #e0e0e0); + transition: background 0.15s ease, color 0.15s ease; + user-select: none; + line-height: 1.4; + } + .horizon-segment:last-child { + border-right: none; + } + .horizon-segment:hover:not(.active) { + background: var(--secondary-background-color, #f5f5f5); + } + .horizon-segment.active { + background: var(--primary-color, #03a9f4); + color: #fff; + font-weight: 600; + } + .horizon-segment.referenced { + box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4); + } + .monitoring-header { display: flex; align-items: center; @@ -404,75 +440,64 @@ class SpanSidePanel extends HTMLElement { const graphInfo = cfg.graphHorizonInfo; const hasOverride = graphInfo?.has_override === true; const currentHorizon = graphInfo?.horizon || DEFAULT_GRAPH_HORIZON; + const globalHorizon = graphInfo?.globalHorizon || DEFAULT_GRAPH_HORIZON; - // Global / Custom radio - const radioGroup = document.createElement("div"); - radioGroup.className = "radio-group"; - radioGroup.innerHTML = ` - - - `; - section.appendChild(radioGroup); + // Segmented button bar: Global | 5m | 1h | 1d | 1w | 1M + const bar = document.createElement("div"); + bar.className = "horizon-bar"; - // Horizon dropdown - const selectWrap = document.createElement("div"); - selectWrap.dataset.role = "graph-horizon-fields"; - selectWrap.style.display = hasOverride ? "block" : "none"; + const segments = [{ key: "global", label: t("sidepanel.global") }]; + for (const key of Object.keys(GRAPH_HORIZONS)) { + segments.push({ key, label: key }); + } - const row = document.createElement("div"); - row.className = "field-row"; + const activeKey = hasOverride ? currentHorizon : "global"; - const label = document.createElement("span"); - label.className = "field-label"; - label.textContent = t("settings.default_scale"); + const updateSegmentStates = newActiveKey => { + for (const btn of bar.querySelectorAll(".horizon-segment")) { + const key = btn.dataset.horizon; + btn.classList.toggle("active", key === newActiveKey); + btn.classList.toggle("referenced", newActiveKey === "global" && key === globalHorizon); + } + }; - const selectEl = document.createElement("select"); - selectEl.dataset.role = "graph-horizon-select"; - for (const key of Object.keys(GRAPH_HORIZONS)) { - const opt = document.createElement("option"); - opt.value = key; - opt.textContent = t(`horizon.${key}`); - if (key === currentHorizon) opt.selected = true; - selectEl.appendChild(opt); - } + for (const { key, label } of segments) { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "horizon-segment"; + btn.dataset.horizon = key; + btn.textContent = label; + btn.classList.toggle("active", key === activeKey); + btn.classList.toggle("referenced", activeKey === "global" && key === globalHorizon); - row.appendChild(label); - row.appendChild(selectEl); - selectWrap.appendChild(row); - section.appendChild(selectWrap); + btn.addEventListener("click", () => { + if (btn.classList.contains("active")) return; - // Event: radio change - const radios = radioGroup.querySelectorAll('input[type="radio"]'); - for (const radio of radios) { - radio.addEventListener("change", () => { - const isCustom = radio.value === "custom" && radio.checked; - selectWrap.style.display = isCustom ? "block" : "none"; - if (!isCustom && radio.checked) { - const circuitId = cfg.uuid; + const circuitId = cfg.uuid; + if (key === "global") { + updateSegmentStates("global"); this._callDomainService("clear_circuit_graph_horizon", { circuit_id: circuitId }) .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) .catch(err => this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${err.message ?? err}`)); + } else { + updateSegmentStates(key); + this._callDomainService("set_circuit_graph_horizon", { + circuit_id: circuitId, + horizon: key, + }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); } }); - } - // Event: dropdown change (debounced) - selectEl.addEventListener("change", () => { - this._debounce("graph-horizon", DEBOUNCE_MS, () => { - const circuitId = cfg.uuid; - this._callDomainService("set_circuit_graph_horizon", { - circuit_id: circuitId, - horizon: selectEl.value, - }) - .then(() => { - this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); - }) - .catch(err => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); - }); - }); + bar.appendChild(btn); + } + section.appendChild(bar); body.appendChild(section); } diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 45261a4..568eb51 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -226,9 +226,10 @@ export class DashboardTab { await this._graphSettingsCache.fetch(this._hass); const graphSettings = this._graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; const graphHorizonInfo = graphSettings?.circuits?.[uuid] - ? graphSettings.circuits[uuid] - : { horizon: graphSettings?.global_horizon || "5m", has_override: false }; + ? { ...graphSettings.circuits[uuid], globalHorizon } + : { horizon: globalHorizon, has_override: false, globalHorizon }; sidePanel.open({ ...circuit, From 49abd00961883c19570b487313ef602e5e3a2704 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:58:40 -0700 Subject: [PATCH 051/101] feat: add shedding priority legend to panel header Render icon key below firmware/unit toggle showing Always On, Never, SoC Threshold, and Off-Grid with their corresponding icons and colors. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 10 +++++++++- src/constants.js | 2 +- src/core/header-renderer.js | 27 +++++++++++++++++++++++---- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index c761192..6cc189d 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function B(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(V(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=B(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function B(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(V(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=B(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index e520985..2445b1a 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:shield-check",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function R(e){return H(e,L)}function j(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n `}(r,s),g=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),m=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),f=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${g}\n ${!1!==s.show_panel?`\n
\n ${m}\n
\n `:""}\n ${f?`
${f}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index eef5c5e..dba17de 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -52,9 +52,17 @@ export const CARD_STYLES = ` .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); } .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); } - .header-right { display: flex; gap: 20px; align-items: center; padding-top: 8px; } + .header-right { display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding-top: 8px; align-self: stretch; } + .header-right-top { display: flex; gap: 20px; align-items: center; } .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); } + .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; } + .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; } + .shedding-legend-item ha-icon { --mdc-icon-size: 16px; } + .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; } + .shedding-legend-text { font-size: 9px; font-weight: 600; } + .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); } + .panel-gear { background: none; border: none; diff --git a/src/constants.js b/src/constants.js index bccb358..e7dedc0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -73,7 +73,7 @@ export const BESS_CHART_METRICS = { export const SHEDDING_PRIORITIES = { always_on: { icon: "mdi:battery", icon2: "mdi:router-wireless", color: "#4caf50", label: () => t("shedding.always_on") }, - never: { icon: "mdi:shield-check", color: "#4caf50", label: () => t("shedding.never") }, + never: { icon: "mdi:battery", color: "#4caf50", label: () => t("shedding.never") }, soc_threshold: { icon: "mdi:battery-alert-variant-outline", color: "#9c27b0", label: () => t("shedding.soc_threshold"), textLabel: "SoC" }, off_grid: { icon: "mdi:transmission-tower", color: "#ff9800", label: () => t("shedding.off_grid") }, unknown: { icon: "mdi:help-circle-outline", color: "#888", label: () => t("shedding.unknown") }, diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index 246105f..89d6d73 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -1,5 +1,6 @@ import { escapeHtml } from "../helpers/sanitize.js"; import { t } from "../i18n.js"; +import { SHEDDING_PRIORITIES } from "../constants.js"; /** * Build the panel header HTML with stats, gear icon, and A/W toggle. @@ -105,10 +106,28 @@ export function buildHeaderHTML(topology, config) {
- ${firmware} -
- - +
+ ${firmware} +
+ + +
+
+
+ ${Object.entries(SHEDDING_PRIORITIES) + .filter(([key]) => key !== "unknown") + .map(([, cfg]) => { + let icons; + if (cfg.icon2) { + icons = ``; + } else if (cfg.textLabel) { + icons = `${cfg.textLabel}`; + } else { + icons = ``; + } + return `
${icons}${cfg.label()}
`; + }) + .join("")}
From 5adfb49af372d5dcda9cd586abb9d85bcf397771 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:29:48 -0700 Subject: [PATCH 052/101] Add lifecycle visibility handling for panel and card - Add connectedCallback/disconnectedCallback to SpanPanelElement to re-render when HA navigates back to the panel - Add visibilitychange listener to both panel and card to refresh charts when browser tab regains focus - Clean up event listeners in disconnectedCallback to prevent leaks - Re-trigger updateDOM in card connectedCallback when already rendered --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/span-panel-card.js | 16 ++++++++++++++++ src/panel/span-panel.js | 22 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 6cc189d..56c65d3 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function T(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function B(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function V(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(V(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=T(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=T(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=T(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?G(d):null,m=g?O(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=B(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function G(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=O(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?O(d):null,m=g?G(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=V(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 2445b1a..3124898 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 17e2c06..09dd0b4 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -69,6 +69,18 @@ export class SpanPanelCard extends HTMLElement { // Will refresh on next interval } }, 30000); + + // Re-render when card is reconnected after navigation + if (this._discovered && this._hass && this._rendered) { + this._updateDOM(); + } + + this._onVisibilityChange = () => { + if (document.visibilityState === "visible" && this._discovered && this._hass) { + this._updateDOM(); + } + }; + document.addEventListener("visibilitychange", this._onVisibilityChange); } disconnectedCallback() { @@ -80,6 +92,10 @@ export class SpanPanelCard extends HTMLElement { clearInterval(this._recorderRefreshInterval); this._recorderRefreshInterval = null; } + if (this._onVisibilityChange) { + document.removeEventListener("visibilitychange", this._onVisibilityChange); + this._onVisibilityChange = null; + } } setConfig(config) { diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 9e75423..181d4d4 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -71,6 +71,28 @@ export class SpanPanelElement extends HTMLElement { this._settingsTab = new SettingsTab(); } + connectedCallback() { + // When HA navigates back to this panel, re-render if we already have data + if (this._discovered && this._hass) { + this._render(); + } + + this._onVisibilityChange = () => { + if (document.visibilityState === "visible" && this._discovered && this._hass) { + this._renderTab(); + } + }; + document.addEventListener("visibilitychange", this._onVisibilityChange); + } + + disconnectedCallback() { + this._dashboardTab.stop(); + if (this._onVisibilityChange) { + document.removeEventListener("visibilitychange", this._onVisibilityChange); + this._onVisibilityChange = null; + } + } + set hass(val) { this._hass = val; this._dashboardTab._hass = val; From 457494c475c7ce9d278cbae07ae44c3ba230237d Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:00:03 -0700 Subject: [PATCH 053/101] feat: use HA native app layout with ha-menu-button for sidebar integration Replace custom self-contained layout with HA's native panel pattern: - Use for proper sidebar toggle on mobile - Accept narrow property from HA for responsive behavior - Style header with HA app header CSS variables - Move tabs into header bar matching HA panel conventions - Content area centered at max-width with full-width header --- dist/span-panel.js | 2 +- src/panel/span-panel.js | 148 +++++++++++++++++++++++++++------------- 2 files changed, 101 insertions(+), 49 deletions(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index 3124898..03e23c3 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e,this._discovered||this._discoverPanels()}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n ${o?`\n \n `:`${s}`}\n
\n\n
\n \n \n \n
\n\n
\n `;const r=this.shadowRoot.getElementById("panel-select");r&&r.addEventListener("change",()=>{this._selectedPanelId=r.value,localStorage.setItem("span_panel_selected",r.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 181d4d4..99bb306 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -7,49 +7,73 @@ import { SettingsTab } from "./tab-settings.js"; const PANEL_STYLES = ` :host { - display: block; - padding: 16px; - max-width: 900px; - margin: 0 auto; + color: var(--primary-text-color); + } + .header { + background-color: var(--app-header-background-color); + color: var(--app-header-text-color, white); + border-bottom: var(--app-header-border-bottom, none); + } + .toolbar { + height: var(--header-height); + display: flex; + align-items: center; + font-size: 20px; + padding: 0 16px; + font-weight: 400; + box-sizing: border-box; + } + .main-title { + margin: 0 0 0 24px; + line-height: 20px; + flex-grow: 1; + } + .panel-selector select { + background: transparent; + border: none; + color: inherit; + font-size: inherit; + font-weight: inherit; + cursor: pointer; + padding: 0; + appearance: none; + -webkit-appearance: none; + } + .panel-selector select option { + background: var(--card-background-color, #333); + color: var(--primary-text-color); } .panel-tabs { + margin-left: max(env(safe-area-inset-left), 24px); + margin-right: max(env(safe-area-inset-right), 24px); display: flex; gap: 0; - border-bottom: 2px solid var(--divider-color, #333); - margin-bottom: 16px; } .panel-tab { padding: 8px 20px; cursor: pointer; font-size: 0.9em; font-weight: 500; - color: var(--secondary-text-color); + color: var(--app-header-text-color, white); + opacity: 0.7; border-bottom: 2px solid transparent; - margin-bottom: -2px; background: none; border-top: none; border-left: none; border-right: none; } .panel-tab.active { - color: var(--primary-color); - border-bottom-color: var(--primary-color); - } - .panel-selector { - margin-bottom: 16px; + opacity: 1; + border-bottom-color: var(--app-header-text-color, white); } - .panel-selector select { - background: var(--secondary-background-color, #333); - border: 1px solid var(--divider-color); - color: var(--primary-text-color); - border-radius: 4px; - padding: 6px 12px; - font-size: 0.9em; + .view { + display: flex; + justify-content: center; + padding: 16px; } - .panel-label { - font-size: 0.9em; - font-weight: 500; - color: var(--secondary-text-color); + .view-content { + width: 100%; + max-width: 900px; } .tab-content { min-height: 400px; @@ -66,6 +90,7 @@ export class SpanPanelElement extends HTMLElement { this._selectedPanelId = null; this._activeTab = "dashboard"; this._discovered = false; + this._narrow = false; this._dashboardTab = new DashboardTab(); this._monitoringTab = new MonitoringTab(); this._settingsTab = new SettingsTab(); @@ -96,11 +121,20 @@ export class SpanPanelElement extends HTMLElement { set hass(val) { this._hass = val; this._dashboardTab._hass = val; + // Update ha-menu-button if already rendered + const menuBtn = this.shadowRoot.querySelector("ha-menu-button"); + if (menuBtn) menuBtn.hass = val; if (!this._discovered) { this._discoverPanels(); } } + set narrow(val) { + this._narrow = val; + const menuBtn = this.shadowRoot.querySelector("ha-menu-button"); + if (menuBtn) menuBtn.narrow = val; + } + setConfig(config) { this._config = config || {}; } @@ -135,35 +169,53 @@ export class SpanPanelElement extends HTMLElement { this.shadowRoot.innerHTML = ` -
- ${ - multiPanel - ? ` - - ` - : `${panelLabel}` - } -
+
+
+ +
+ + ${ + multiPanel + ? ` + + ` + : panelLabel + } + +
+
-
- - - +
+ + + +
-
+
+
+
+
+
`; + // Wire up ha-menu-button + const menuBtn = this.shadowRoot.querySelector("ha-menu-button"); + if (menuBtn) { + menuBtn.hass = this._hass; + menuBtn.narrow = this._narrow; + } + const select = this.shadowRoot.getElementById("panel-select"); if (select) { select.addEventListener("change", () => { From 9f07a9a2014727acd8b0c44debc73b504ca588b5 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:34:38 -0700 Subject: [PATCH 054/101] fix: prevent blank charts on recorder refresh and duplicate element registration - Load recorder history into a temp map before swapping into powerHistory, so charts retain stale data during the async fetch instead of going blank - Guard customElements.define for span-side-panel to prevent duplicate registration when both card and panel bundles are loaded - Early-exit in discoverTopology when deviceId is null/undefined to avoid sending invalid WS calls --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-discovery.js | 3 +++ src/card/span-panel-card.js | 17 +++++++++++++---- src/core/side-panel.js | 4 +++- src/panel/tab-dashboard.js | 18 +++++++++++++----- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 56c65d3..7c1b1e3 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function G(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=O(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0!==e.size){for(const t of e.keys())this._powerHistory.delete(t);try{await K(this._hass,this._topology,this._config,this._powerHistory,e),this._updateDOM()}catch{}}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?O(d):null,m=g?G(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=V(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function G(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=O(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await K(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?O(d):null,m=g?G(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=V(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 03e23c3..ddb3473 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0!==t.size){for(const e of t.keys())this._powerHistory.delete(e);try{await ne(this._hass,this._topology,this._config,this._powerHistory,t),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-discovery.js b/src/card/card-discovery.js index a778881..9b730f6 100644 --- a/src/card/card-discovery.js +++ b/src/card/card-discovery.js @@ -4,6 +4,9 @@ import { t } from "../i18n.js"; // ── Primary discovery via custom WebSocket API ─────────────────────────────── export async function discoverTopology(hass, deviceId) { + if (!deviceId) { + throw new Error(t("card.device_not_found")); + } const topology = await hass.callWS({ type: `${INTEGRATION_DOMAIN}/panel_topology`, device_id: deviceId, diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 09dd0b4..6546577 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -59,11 +59,20 @@ export class SpanPanelCard extends HTMLElement { } } if (nonRealtimeMap.size === 0) return; - for (const uuid of nonRealtimeMap.keys()) { - this._powerHistory.delete(uuid); - } + // Load into a temporary map so charts keep showing stale data + // until fresh data is ready (avoids blank-chart flash). + const freshHistory = new Map(); try { - await loadHistory(this._hass, this._topology, this._config, this._powerHistory, nonRealtimeMap); + await loadHistory(this._hass, this._topology, this._config, freshHistory, nonRealtimeMap); + // Atomically replace only the non-realtime entries + for (const uuid of nonRealtimeMap.keys()) { + const data = freshHistory.get(uuid); + if (data) { + this._powerHistory.set(uuid, data); + } else { + this._powerHistory.delete(uuid); + } + } this._updateDOM(); } catch { // Will refresh on next interval diff --git a/src/core/side-panel.js b/src/core/side-panel.js index d27c33a..44bfe04 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -739,4 +739,6 @@ class SpanSidePanel extends HTMLElement { } } -customElements.define("span-side-panel", SpanSidePanel); +if (!customElements.get("span-side-panel")) { + customElements.define("span-side-panel", SpanSidePanel); +} diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 568eb51..eadd374 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -140,12 +140,20 @@ export class DashboardTab { } } if (nonRealtimeMap.size === 0) return; - // Clear and reload history for non-realtime circuits - for (const uuid of nonRealtimeMap.keys()) { - this._powerHistory.delete(uuid); - } + // Load into a temporary map so charts keep showing stale data + // until fresh data is ready (avoids blank-chart flash). + const freshHistory = new Map(); try { - await loadHistory(this._hass, this._topology, this._config, this._powerHistory, nonRealtimeMap); + await loadHistory(this._hass, this._topology, this._config, freshHistory, nonRealtimeMap); + // Atomically replace only the non-realtime entries + for (const uuid of nonRealtimeMap.keys()) { + const data = freshHistory.get(uuid); + if (data) { + this._powerHistory.set(uuid, data); + } else { + this._powerHistory.delete(uuid); + } + } updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); } catch { // Recorder data will refresh on next interval From 45779a8d68f86d8ecfc066ffdba4e2cab795be06 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:48:36 -0700 Subject: [PATCH 055/101] feat: add slide-to-confirm safety switch for circuit toggles Add a slide-to-confirm widget in the panel header that must be dragged fully across (90% threshold) before circuit toggle pills become active. Partial slides snap back with animation. Click-to-relock supported. - Greyed-out pills with pointer-events disabled when locked - Touch and mouse drag support - i18n labels in en, es, fr, ja, pt - Optimistic UI update on toggle click (immediate pill flip) --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 64 ++++++++++++++++++++++++++++++ src/card/span-panel-card.js | 79 +++++++++++++++++++++++++++++++++++++ src/core/header-renderer.js | 8 ++++ src/i18n.js | 10 +++++ src/panel/tab-dashboard.js | 75 +++++++++++++++++++++++++++++++++++ 7 files changed, 238 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 7c1b1e3..d1e01c9 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",l="bess",c="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function m(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,l,c,d,p,u){const f=n.entities?.power,m=f?c.states[f]:null,v=m&&parseFloat(m.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?c.states[y]:null,C=w?"on"===w.state:(m?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=_(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?c.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const N=h[u||"unknown"]||h.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function G(e){return F(e,H)}function W(e){return F(e,I)}function q(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=a.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),l=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,l))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=O(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function K(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):m(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=m(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of J(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function X(e,t,i,o,s,a,r,l){const{options:c,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,l=Date.now(),c=l-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=i.fixedMin,_.max=i.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===i.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[c,.8*s],[l,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,s],[l,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,l);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=c,u.data=d}function Q(e,n,i,o,s,l){if(!e||!i||!n)return;const c=m(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=$(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,_=f&&parseFloat(f.state)||0,m=d.device_type===r||_<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(_)}${z(_)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;X(N,n,e,l?.has(o)?v(l.get(o)):c,p,m,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===l&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",n)),c.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;c.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=p.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await K(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return m(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),l=i.filter(e=>e.via_device_id===n),c=new Set(l.map(e=>e.id)),d=o.filter(e=>c.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let l=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}u&&l.startsWith(u+" ")&&(l=l.slice(u.length+1));const c=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:l,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${c}_breaker`,breaker_rating:`sensor.${c}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of l){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],l=r&&parseFloat(r.state)||0,c=v(s),d=b(c),p=e-c;y(this._powerHistory,t,l,e,p,d)}const t=m(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of J(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=m(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");X(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;e.stopPropagation(),e.preventDefault();const n=t.closest("[data-uuid]");if(!n||!this._topology||!this._hass)return;const i=n.dataset.uuid,o=this._topology.circuits[i];if(!o)return;const s=o.entities?.switch;if(!s)return;const a=this._hass.states[s];if(!a)return void console.warn("SPAN Panel: switch entity not found:",s);const r="on"===a.state?"turn_off":"turn_on";this._hass.callService("switch",r,{},{entity_id:s}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,l=r?.global_horizon||i,c=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:c})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${_(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=_(e.device_name||t("header.default_name")),o=_(e.serial||""),s=_(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!l.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===l&&!o)continue;if(d.type===c&&!s)continue;const e=d.type===c?t("subdevice.ev_charger"):d.type===l?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===l,f=g?O(d):null,m=g?G(d):null,v=g?W(d):null,b=q(d,n,i,new Set([p,f,m,v].filter(Boolean))),y=V(r,0,g,p,f,m);a+=`\n
\n
\n ${_(e)}\n ${_(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const l=document.createElement("option");if(l.value="",l.textContent=t("editor.select_panel"),r.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=n+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=c(d,"0","30",t("editor.days")),g=c(p,"0","23",t("editor.hours")),f=c(u,"0","59",t("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let l=o.original_name||i;const c=n.name||"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),r.textContent=l,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,u){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const A=h[u||"unknown"]||h.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function q(e){return F(e,H)}function W(e){return F(e,I)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=O(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(m)}${z(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const A=i.querySelector(".chart-container");if(A){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(A,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new N,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,f=g?O(d):null,_=g?q(d):null,v=g?W(d):null,b=G(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index ddb3473..e29fcae 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const T=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),L=T?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},L={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,T)}function j(e){return H(e,L)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function X(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,X(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{const n=e.target.closest(".toggle-pill");if(!n)return;e.stopPropagation(),e.preventDefault();const i=n.closest("[data-uuid]");if(!i||!t||!this._hass)return;const o=i.dataset.uuid,a=t.circuits[o];if(!a)return;const s=a.entities?.switch;if(!s)return;const r=this._hass.states[s];if(!r)return;const l="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",l,{},{entity_id:s})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let L;L=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const N=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=N?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${q}\n ${I}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,N)}function j(e){return H(e,T)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index dba17de..f238690 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -74,6 +74,70 @@ export const CARD_STYLES = ` vertical-align: middle; } .panel-gear:hover { opacity: 1; } + .header-center { + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 8px; + } + .slide-confirm { + position: relative; + display: inline-flex; + align-items: center; + width: 160px; + height: 28px; + border-radius: 14px; + background: color-mix(in srgb, var(--primary-color, #4dd9af) 20%, var(--secondary-background-color, #333)); + vertical-align: middle; + overflow: hidden; + user-select: none; + touch-action: none; + } + .slide-confirm-text { + position: absolute; + width: 100%; + text-align: center; + font-size: 0.65em; + font-weight: 600; + color: var(--secondary-text-color, #999); + pointer-events: none; + z-index: 0; + } + .slide-confirm-knob { + position: absolute; + left: 2px; + top: 2px; + width: 24px; + height: 24px; + border-radius: 50%; + background: var(--secondary-text-color, #666); + display: flex; + align-items: center; + justify-content: center; + cursor: grab; + z-index: 1; + transition: none; + } + .slide-confirm-knob ha-icon { + --mdc-icon-size: 14px; + color: var(--card-background-color, #1c1c1c); + } + .slide-confirm-knob.snapping { + transition: left 0.25s ease; + } + .slide-confirm.confirmed { + background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent); + } + .slide-confirm.confirmed .slide-confirm-text { + color: var(--state-active-color, var(--span-accent)); + } + .slide-confirm.confirmed .slide-confirm-knob { + background: var(--state-active-color, var(--span-accent)); + } + .switches-disabled .toggle-pill { + opacity: 0.3; + pointer-events: none; + } .unit-toggle { display: inline-flex; background: var(--secondary-background-color, #333); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 6546577..6d199b8 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -294,11 +294,83 @@ export class SpanPanelCard extends HTMLElement { this._updateDOM(); } + // ── Slide-to-confirm safety switch ───────────────────────────────────────── + + _bindSlideConfirm(slideEl, parent) { + const knob = slideEl.querySelector(".slide-confirm-knob"); + const textEl = slideEl.querySelector(".slide-confirm-text"); + if (!knob) return; + const THRESHOLD = 0.9; + let dragging = false; + let startX = 0; + let maxX = 0; + + const begin = clientX => { + if (slideEl.classList.contains("confirmed")) return; + dragging = true; + startX = clientX - knob.offsetLeft; + maxX = slideEl.offsetWidth - knob.offsetWidth - 4; + knob.classList.remove("snapping"); + }; + const move = clientX => { + if (!dragging) return; + const x = Math.max(2, Math.min(clientX - startX, maxX)); + knob.style.left = x + "px"; + }; + const end = () => { + if (!dragging) return; + dragging = false; + const pos = (knob.offsetLeft - 2) / maxX; + if (pos >= THRESHOLD) { + knob.style.left = maxX + "px"; + slideEl.classList.add("confirmed"); + knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock-open"); + textEl.textContent = slideEl.dataset.textOn; + if (parent) parent.classList.remove("switches-disabled"); + } else { + knob.classList.add("snapping"); + knob.style.left = "2px"; + } + }; + + knob.addEventListener("mousedown", e => { + e.preventDefault(); + begin(e.clientX); + }); + slideEl.addEventListener("mousemove", e => move(e.clientX)); + slideEl.addEventListener("mouseup", end); + slideEl.addEventListener("mouseleave", end); + knob.addEventListener( + "touchstart", + e => { + e.preventDefault(); + begin(e.touches[0].clientX); + }, + { passive: false } + ); + slideEl.addEventListener("touchmove", e => move(e.touches[0].clientX), { passive: true }); + slideEl.addEventListener("touchend", end); + slideEl.addEventListener("touchcancel", end); + + // Click the confirmed slider to re-lock + slideEl.addEventListener("click", () => { + if (!slideEl.classList.contains("confirmed")) return; + slideEl.classList.remove("confirmed"); + knob.classList.add("snapping"); + knob.style.left = "2px"; + knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock"); + textEl.textContent = slideEl.dataset.textOff; + if (parent) parent.classList.add("switches-disabled"); + }); + } + // ── Toggle click handler ─────────────────────────────────────────────────── _onToggleClick(ev) { const pill = ev.target.closest(".toggle-pill"); if (!pill) return; + const cb = this.shadowRoot.querySelector(".slide-confirm"); + if (!cb || !cb.classList.contains("confirmed")) return; ev.stopPropagation(); ev.preventDefault(); const slot = pill.closest("[data-uuid]"); @@ -436,6 +508,13 @@ export class SpanPanelCard extends HTMLElement { this.shadowRoot.addEventListener("click", this._handleGearClick); this.shadowRoot.addEventListener("graph-settings-changed", this._handleGraphSettingsChanged); + const slideEl = this.shadowRoot.querySelector(".slide-confirm"); + if (slideEl) { + this._bindSlideConfirm(slideEl, this.shadowRoot.querySelector("ha-card")); + const card = this.shadowRoot.querySelector("ha-card"); + if (card) card.classList.add("switches-disabled"); + } + const sidePanel = this.shadowRoot.querySelector("span-side-panel"); if (sidePanel) sidePanel.hass = hass; diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index 89d6d73..79d823d 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -105,6 +105,14 @@ export function buildHeaderHTML(topology, config) { }
+
+
+ ${t("header.enable_switches")} +
+ +
+
+
${firmware} diff --git a/src/i18n.js b/src/i18n.js index e1cbc6b..76f32f2 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -93,6 +93,8 @@ const translations = { "header.solar": "Solar", "header.battery": "Battery", "header.toggle_units": "Toggle Watts / Amps", + "header.enable_switches": "Enable Switches", + "header.switches_enabled": "Switches Enabled", // Grid "grid.unknown": "Unknown", @@ -247,6 +249,8 @@ const translations = { "header.solar": "Solar", "header.battery": "Bater\u00eda", "header.toggle_units": "Alternar Watts / Amperios", + "header.enable_switches": "Habilitar Interruptores", + "header.switches_enabled": "Interruptores Habilitados", "grid.unknown": "Desconocido", "grid.configure": "Configurar circuito", "grid.on": "Enc", @@ -388,6 +392,8 @@ const translations = { "header.solar": "Solaire", "header.battery": "Batterie", "header.toggle_units": "Basculer Watts / Amp\u00e8res", + "header.enable_switches": "Activer les interrupteurs", + "header.switches_enabled": "Interrupteurs activ\u00e9s", "grid.unknown": "Inconnu", "grid.configure": "Configurer le circuit", "grid.on": "On", @@ -531,6 +537,8 @@ const translations = { "header.solar": "\u30bd\u30fc\u30e9\u30fc", "header.battery": "\u30d0\u30c3\u30c6\u30ea\u30fc", "header.toggle_units": "\u30ef\u30c3\u30c8/\u30a2\u30f3\u30da\u30a2\u5207\u308a\u66ff\u3048", + "header.enable_switches": "\u30b9\u30a4\u30c3\u30c1\u3092\u6709\u52b9\u5316", + "header.switches_enabled": "\u30b9\u30a4\u30c3\u30c1\u6709\u52b9", "grid.unknown": "\u4e0d\u660e", "grid.configure": "\u56de\u8def\u3092\u8a2d\u5b9a", "grid.on": "\u30aa\u30f3", @@ -676,6 +684,8 @@ const translations = { "header.solar": "Solar", "header.battery": "Bateria", "header.toggle_units": "Alternar Watts / Amperes", + "header.enable_switches": "Ativar Interruptores", + "header.switches_enabled": "Interruptores Ativados", "grid.unknown": "Desconhecido", "grid.configure": "Configurar circuito", "grid.on": "Lig", diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index eadd374..a44f28f 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -123,6 +123,12 @@ export class DashboardTab { updateCircuitDOM(container, hass, topo, config, this._powerHistory, this._horizonMap); updateSubDeviceDOM(container, hass, topo, config, this._powerHistory); + const slideEl = container.querySelector(".slide-confirm"); + if (slideEl) { + this._bindSlideConfirm(slideEl, container); + container.classList.add("switches-disabled"); + } + // Start live update loop this._updateInterval = setInterval(() => { this._recordSamples(); @@ -188,10 +194,79 @@ export class DashboardTab { } } + _bindSlideConfirm(slideEl, parent) { + const knob = slideEl.querySelector(".slide-confirm-knob"); + const textEl = slideEl.querySelector(".slide-confirm-text"); + if (!knob) return; + const THRESHOLD = 0.9; + let dragging = false; + let startX = 0; + let maxX = 0; + + const begin = clientX => { + if (slideEl.classList.contains("confirmed")) return; + dragging = true; + startX = clientX - knob.offsetLeft; + maxX = slideEl.offsetWidth - knob.offsetWidth - 4; + knob.classList.remove("snapping"); + }; + const move = clientX => { + if (!dragging) return; + const x = Math.max(2, Math.min(clientX - startX, maxX)); + knob.style.left = x + "px"; + }; + const end = () => { + if (!dragging) return; + dragging = false; + const pos = (knob.offsetLeft - 2) / maxX; + if (pos >= THRESHOLD) { + knob.style.left = maxX + "px"; + slideEl.classList.add("confirmed"); + knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock-open"); + textEl.textContent = slideEl.dataset.textOn; + if (parent) parent.classList.remove("switches-disabled"); + } else { + knob.classList.add("snapping"); + knob.style.left = "2px"; + } + }; + + knob.addEventListener("mousedown", e => { + e.preventDefault(); + begin(e.clientX); + }); + slideEl.addEventListener("mousemove", e => move(e.clientX)); + slideEl.addEventListener("mouseup", end); + slideEl.addEventListener("mouseleave", end); + knob.addEventListener( + "touchstart", + e => { + e.preventDefault(); + begin(e.touches[0].clientX); + }, + { passive: false } + ); + slideEl.addEventListener("touchmove", e => move(e.touches[0].clientX), { passive: true }); + slideEl.addEventListener("touchend", end); + slideEl.addEventListener("touchcancel", end); + + slideEl.addEventListener("click", () => { + if (!slideEl.classList.contains("confirmed")) return; + slideEl.classList.remove("confirmed"); + knob.classList.add("snapping"); + knob.style.left = "2px"; + knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock"); + textEl.textContent = slideEl.dataset.textOff; + if (parent) parent.classList.add("switches-disabled"); + }); + } + _bindToggleClicks(container, topology) { container.addEventListener("click", e => { const pill = e.target.closest(".toggle-pill"); if (!pill) return; + const cb = container.querySelector(".slide-confirm"); + if (!cb || !cb.classList.contains("confirmed")) return; e.stopPropagation(); e.preventDefault(); const slot = pill.closest("[data-uuid]"); From ba3aefe9afb223f32fc1269bf71ef037706330f5 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:50:44 -0700 Subject: [PATCH 056/101] fix: use ResizeObserver for responsive layout instead of viewport media query The @media breakpoint checked viewport width, not card width, so the narrow layout never triggered when the card was rendered in a narrow container (e.g. editor preview or single-column section). A ResizeObserver now toggles a .narrow class on ha-card based on actual element width, with an immediate check in _render() to avoid the race on first paint after cache clear. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 24 ++++++++++++------------ src/card/span-panel-card.js | 21 +++++++++++++++++++++ 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index d1e01c9..fbde5d9 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,u){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const A=h[u||"unknown"]||h.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function q(e){return F(e,H)}function W(e){return F(e,I)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=O(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(m)}${z(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const A=i.querySelector(".chart-container");if(A){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(A,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new N,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,f=g?O(d):null,_=g?q(d):null,v=g?W(d):null,b=G(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function S(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class C{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,u){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,S=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,C=n.breaker_rating_a,k=C?`${Math.round(C)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const A=h[u||"unknown"]||h.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function O(e){return F(e,D)}function j(e){return F(e,R)}function q(e){return F(e,H)}function W(e){return F(e,I)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:O(i)};i.type===c&&(e.soc=j(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=S(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(m)}${z(m)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let C;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)C="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;C=t?t.state:"unknown"}const k=h[C]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const A=i.querySelector(".chart-container");if(A){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(A,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new N,this._graphSettingsCache=new C,this._horizonMap=new Map,this._resizeObserver=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._resizeObserver=new ResizeObserver(e=>{for(const t of e){const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.toggle("narrow",t.contentRect.width<600)}}),this._resizeObserver.observe(this),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=S(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=O(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=O(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,f=g?j(d):null,_=g?q(d):null,v=g?W(d):null,b=G(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `;const p=this.shadowRoot.querySelector("ha-card");p&&p.classList.toggle("narrow",this.offsetWidth<600),this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const u=this.shadowRoot.querySelector(".slide-confirm");if(u){this._bindSlideConfirm(u,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const g=this.shadowRoot.querySelector("span-side-panel");g&&(g.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index e29fcae..4fc609c 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let L;L=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const N=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=N?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${q}\n ${I}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,N)}function j(e){return H(e,T)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let L;L=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const N=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=N?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${q}\n ${I}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,N)}function j(e){return H(e,T)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index f238690..45160c1 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -390,16 +390,16 @@ export const CARD_STYLES = ` .sub-entity-name { color: var(--secondary-text-color, #999); } .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); } - @media (max-width: 600px) { - ha-card { padding: 12px; } - .panel-header { flex-direction: column; } - .panel-identity { flex-direction: column; gap: 4px; } - .panel-title { font-size: 1.4em; } - .panel-stats { gap: 16px; flex-wrap: wrap; } - .header-right { margin-top: 8px; } - .circuit-slot { min-height: 100px; padding: 10px 12px 16px; } - .circuit-col-span { min-height: 200px; } - .chart-container { height: 60px; } - .circuit-col-span .chart-container { height: 140px; } - } + ha-card.narrow { padding: 12px; } + .narrow .panel-header { flex-direction: column; } + .narrow .panel-identity { flex-direction: column; gap: 4px; } + .narrow .panel-title { font-size: 1.4em; } + .narrow .panel-stats { gap: 16px; flex-wrap: wrap; } + .narrow .header-center { margin-top: 8px; } + .narrow .header-right { margin-top: 8px; align-items: flex-start; } + .narrow .shedding-legend { justify-content: flex-start; } + .narrow .circuit-slot { min-height: 100px; padding: 10px 12px 16px; } + .narrow .circuit-col-span { min-height: 200px; } + .narrow .chart-container { height: 60px; } + .narrow .circuit-col-span .chart-container { height: 140px; } `; diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 6d199b8..0cbfb85 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -41,6 +41,7 @@ export class SpanPanelCard extends HTMLElement { this._monitoringCache = new MonitoringStatusCache(); this._graphSettingsCache = new GraphSettingsCache(); this._horizonMap = new Map(); + this._resizeObserver = null; } connectedCallback() { @@ -84,6 +85,16 @@ export class SpanPanelCard extends HTMLElement { this._updateDOM(); } + this._resizeObserver = new ResizeObserver(entries => { + for (const entry of entries) { + const card = this.shadowRoot.querySelector("ha-card"); + if (card) { + card.classList.toggle("narrow", entry.contentRect.width < 600); + } + } + }); + this._resizeObserver.observe(this); + this._onVisibilityChange = () => { if (document.visibilityState === "visible" && this._discovered && this._hass) { this._updateDOM(); @@ -101,6 +112,10 @@ export class SpanPanelCard extends HTMLElement { clearInterval(this._recorderRefreshInterval); this._recorderRefreshInterval = null; } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } if (this._onVisibilityChange) { document.removeEventListener("visibilitychange", this._onVisibilityChange); this._onVisibilityChange = null; @@ -502,6 +517,12 @@ export class SpanPanelCard extends HTMLElement { `; + // Apply narrow class immediately based on current element width + const card = this.shadowRoot.querySelector("ha-card"); + if (card) { + card.classList.toggle("narrow", this.offsetWidth < 600); + } + // Attach delegated click listeners this.shadowRoot.addEventListener("click", this._handleToggleClick); this.shadowRoot.addEventListener("click", this._handleUnitToggle); From c1c249f7ef81d1d305fd7c780111b966f4b6331c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:00:43 -0700 Subject: [PATCH 057/101] fix: replace ResizeObserver with CSS flex-wrap for responsive header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ResizeObserver + .narrow class approach was unreliable (race on first paint) and didn't apply to the panel view at all. Now the header uses flex-wrap so children wrap naturally based on available container width — no JS needed, works in both card and panel contexts. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 22 ++++++++-------------- src/card/span-panel-card.js | 21 --------------------- 4 files changed, 10 insertions(+), 37 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index fbde5d9..0fa3d5b 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function S(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class C{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,u){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,S=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,C=n.breaker_rating_a,k=C?`${Math.round(C)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const A=h[u||"unknown"]||h.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function O(e){return F(e,D)}function j(e){return F(e,R)}function q(e){return F(e,H)}function W(e){return F(e,I)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:O(i)};i.type===c&&(e.soc=j(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=S(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(m)}${z(m)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let C;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)C="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;C=t?t.state:"unknown"}const k=h[C]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const A=i.querySelector(".chart-container");if(A){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(A,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new N,this._graphSettingsCache=new C,this._horizonMap=new Map,this._resizeObserver=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._resizeObserver=new ResizeObserver(e=>{for(const t of e){const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.toggle("narrow",t.contentRect.width<600)}}),this._resizeObserver.observe(this),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=S(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=O(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=O(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,f=g?j(d):null,_=g?q(d):null,v=g?W(d):null,b=G(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `;const p=this.shadowRoot.querySelector("ha-card");p&&p.classList.toggle("narrow",this.offsetWidth<600),this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const u=this.shadowRoot.querySelector(".slide-confirm");if(u){this._bindSlideConfirm(u,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const g=this.shadowRoot.querySelector("span-side-panel");g&&(g.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,u){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const A=h[u||"unknown"]||h.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function q(e){return F(e,H)}function W(e){return F(e,I)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=O(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(m)}${z(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const A=i.querySelector(".chart-container");if(A){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(A,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new N,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,f=g?O(d):null,_=g?q(d):null,v=g?W(d):null,b=G(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 4fc609c..c2e3545 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let L;L=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const N=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=N?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${q}\n ${I}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,N)}function j(e){return H(e,T)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let L;L=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const N=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=N?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${q}\n ${I}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,N)}function j(e){return H(e,T)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index 45160c1..d2ac24a 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -14,12 +14,17 @@ export const CARD_STYLES = ` .panel-header { display: flex; + flex-wrap: wrap; justify-content: space-between; align-items: flex-start; + gap: 8px 16px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--divider-color, #333); } + .header-left { flex: 1 1 300px; min-width: 0; } + .header-center { flex: 0 0 auto; } + .header-right { flex: 0 1 auto; min-width: 0; } .panel-identity { display: flex; @@ -43,7 +48,8 @@ export const CARD_STYLES = ` .panel-stats { display: flex; - gap: 32px; + flex-wrap: wrap; + gap: 16px 32px; } .stat { display: flex; flex-direction: column; } @@ -52,7 +58,7 @@ export const CARD_STYLES = ` .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); } .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); } - .header-right { display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding-top: 8px; align-self: stretch; } + .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; padding-top: 8px; } .header-right-top { display: flex; gap: 20px; align-items: center; } .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); } @@ -390,16 +396,4 @@ export const CARD_STYLES = ` .sub-entity-name { color: var(--secondary-text-color, #999); } .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); } - ha-card.narrow { padding: 12px; } - .narrow .panel-header { flex-direction: column; } - .narrow .panel-identity { flex-direction: column; gap: 4px; } - .narrow .panel-title { font-size: 1.4em; } - .narrow .panel-stats { gap: 16px; flex-wrap: wrap; } - .narrow .header-center { margin-top: 8px; } - .narrow .header-right { margin-top: 8px; align-items: flex-start; } - .narrow .shedding-legend { justify-content: flex-start; } - .narrow .circuit-slot { min-height: 100px; padding: 10px 12px 16px; } - .narrow .circuit-col-span { min-height: 200px; } - .narrow .chart-container { height: 60px; } - .narrow .circuit-col-span .chart-container { height: 140px; } `; diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 0cbfb85..6d199b8 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -41,7 +41,6 @@ export class SpanPanelCard extends HTMLElement { this._monitoringCache = new MonitoringStatusCache(); this._graphSettingsCache = new GraphSettingsCache(); this._horizonMap = new Map(); - this._resizeObserver = null; } connectedCallback() { @@ -85,16 +84,6 @@ export class SpanPanelCard extends HTMLElement { this._updateDOM(); } - this._resizeObserver = new ResizeObserver(entries => { - for (const entry of entries) { - const card = this.shadowRoot.querySelector("ha-card"); - if (card) { - card.classList.toggle("narrow", entry.contentRect.width < 600); - } - } - }); - this._resizeObserver.observe(this); - this._onVisibilityChange = () => { if (document.visibilityState === "visible" && this._discovered && this._hass) { this._updateDOM(); @@ -112,10 +101,6 @@ export class SpanPanelCard extends HTMLElement { clearInterval(this._recorderRefreshInterval); this._recorderRefreshInterval = null; } - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - this._resizeObserver = null; - } if (this._onVisibilityChange) { document.removeEventListener("visibilitychange", this._onVisibilityChange); this._onVisibilityChange = null; @@ -517,12 +502,6 @@ export class SpanPanelCard extends HTMLElement { `; - // Apply narrow class immediately based on current element width - const card = this.shadowRoot.querySelector("ha-card"); - if (card) { - card.classList.toggle("narrow", this.offsetWidth < 600); - } - // Attach delegated click listeners this.shadowRoot.addEventListener("click", this._handleToggleClick); this.shadowRoot.addEventListener("click", this._handleUnitToggle); From 3cf54d3863a25b60142bc1e8912d9c1d7ac41bd7 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:26:53 -0700 Subject: [PATCH 058/101] feat: replace panel gear placeholder with full graph settings UI The panel gear side panel now shows the actual global horizon default and per-circuit scale table instead of static text with a navigation button. Both card and frontend panel gear now open the same settings side panel, backed by the same integration services. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/span-panel-card.js | 7 +- src/core/side-panel.js | 154 +++++++++++++++++++++++++++++------- src/i18n.js | 54 +++++-------- src/panel/tab-dashboard.js | 7 +- 6 files changed, 161 insertions(+), 65 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 0fa3d5b..7f75a37 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},h={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function z(e){return k.unit(e)}function E(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,u){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${E(v)}${z(v)}`;const A=h[u||"unknown"]||h.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function O(e){return F(e,R)}function q(e){return F(e,H)}function W(e){return F(e,I)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=O(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,u=i.unit(0),h=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=h.length>0?Math.max(...h.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${u}
`}},animation:!1},series:g}}(i,o,s,a,c);let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.height=(r||120)+"px",e.innerHTML="",e.appendChild(u)),u.hass=t,u.options=l,u.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=$(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${E(m)}${z(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=h[S]||h.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const A=i.querySelector(".chart-container");if(A){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(A,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(h).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._createHeader(t("sidepanel.panel_monitoring"),t("sidepanel.global_defaults"));e.appendChild(n);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${t("sidepanel.global_info")}

\n

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const s=document.createElement("button");s.textContent=t("sidepanel.configure_global"),Object.assign(s.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),s.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(s),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=h[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))u.push({key:e,label:e});const h=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",h,n)),u.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),u.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),u.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(u),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${n}`,c&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=a,p.appendChild(u),p.appendChild(h),c||u.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new N,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},u=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}u&&c.startsWith(u+" ")&&(c=c.slice(u.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let h="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(h=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:h,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${E(i)} ${z(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=u.power;n.endsWith("_soc")?a=u.soc:n.endsWith("_soe")&&(a=u.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return void n.open({panelMode:!0});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(h).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),u=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=L(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),u=p?n.states[p]:null,h=u&&parseFloat(u.state)||0,g=d.type===c,f=g?O(d):null,_=g?q(d):null,v=g?W(d):null,b=G(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${E(h)} ${z(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const u=this.shadowRoot.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,u=parseInt(this._config.history_minutes)||0,h=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(u,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(h.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};h.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(h.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=h.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(u).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=t("sidepanel.global_default"),g.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=h.power;n.endsWith("_soc")?a=h.soc:n.endsWith("_soe")&&(a=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),h=p?n.states[p]:null,u=h&&parseFloat(h.state)||0,g=d.type===c,f=g?G(d):null,_=g?O(d):null,v=g?q(d):null,b=W(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${z(u)} ${E(u)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index c2e3545..a14c792 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.panel_monitoring":"Panel Monitoring","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_info":"Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.","sidepanel.custom_info_prefix":"Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to","sidepanel.custom_info_suffix":"mode.","sidepanel.configure_global":"Configure Global Thresholds","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.panel_monitoring":"Monitoreo del Panel","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_info":"Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integración para cambiar la configuración global.","sidepanel.custom_info_prefix":"Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Umbrales Globales","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.panel_monitoring":"Surveillance du Panneau","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_info":"Les seuils de surveillance globaux s'appliquent à tous les circuits sans remplacement personnalisé. Utilisez le flux d'options de l'intégration pour modifier les paramètres globaux.","sidepanel.custom_info_prefix":"Les seuils individuels de circuit peuvent être configurés en cliquant sur l'icône d'engrenage d'une rangée de circuit et en passant au mode","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurer les Seuils Globaux","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.panel_monitoring":"パネルモニタリング","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_info":"グローバル監視シきい値はカスタム上書きのない全回路に適用されます。統合のオプションフローでグローバル設定を変更してください。","sidepanel.custom_info_prefix":"個別の回路しきい値は、回路行の歯車アイコンをクリックしてモードを切り替えて設定できます","sidepanel.custom_info_suffix":"。","sidepanel.configure_global":"グローバルしきい値を設定","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.panel_monitoring":"Monitoramento do Painel","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_info":"Os limites de monitoramento global se aplicam a todos os circuitos sem substituições personalizadas. Use o fluxo de opções da integração para alterar as configurações globais.","sidepanel.custom_info_prefix":"Os limites individuais de circuito podem ser configurados clicando no ícone de engrenagem em uma linha de circuito e alternando para o modo","sidepanel.custom_info_suffix":".","sidepanel.configure_global":"Configurar Limites Globais","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const b=Object.keys(g).filter(e=>"unknown"!==e);class v extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._createHeader(n("sidepanel.panel_monitoring"),n("sidepanel.global_defaults"));e.appendChild(t);const i=document.createElement("div");i.className="panel-body";const o=document.createElement("div");o.className="panel-mode-info",o.innerHTML=`\n

${n("sidepanel.global_info")}

\n

${n("sidepanel.custom_info_prefix")} ${n("sidepanel.custom")} ${n("sidepanel.custom_info_suffix")}

\n `,i.appendChild(o);const a=document.createElement("button");a.textContent=n("sidepanel.configure_global"),Object.assign(a.style,{display:"inline-block",marginTop:"8px",padding:"8px 16px",background:"var(--primary-color, #4dd9af)",color:"var(--text-primary-color, #000)",borderRadius:"4px",border:"none",cursor:"pointer",fontSize:"0.85em",fontWeight:"500"}),a.addEventListener("click",()=>{this.close(),this.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}))}),i.appendChild(a),e.appendChild(i)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",v);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,b=f&&parseFloat(f.state)||0,v=t.device_type===l||b<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(b)}${w(b)}`;const A=g[u||"unknown"]||g.unknown;let L;L=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const N=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=N?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${L}\n ${q}\n ${I}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function H(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function D(e){return H(e,N)}function j(e){return H(e,T)}function R(e){return H(e,I)}function F(e){return H(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function O(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=O(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=O(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=D(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:D(i)};i.type===c&&(e.soc=j(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):O(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=O(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(O(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),b=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=D(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?j(l):null,f=g?R(l):null,b=g?F(l):null,v=G(l,t,i,new Set([p,m,f,b].filter(Boolean))),y=W(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${v}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${b?`
${b}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return void e.dispatchEvent(new CustomEvent("navigate-tab",{detail:"monitoring",bubbles:!0,composed:!0}));const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,b=!1!==a.enable_event_bus,v=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=H(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,f,v].filter(Boolean))),y=O(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${v?`
${v}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 6d199b8..2ada500 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -402,7 +402,12 @@ export class SpanPanelCard extends HTMLElement { sidePanel.hass = this._hass; if (gearBtn.classList.contains("panel-gear")) { - sidePanel.open({ panelMode: true }); + await this._graphSettingsCache.fetch(this._hass); + sidePanel.open({ + panelMode: true, + topology: this._topology, + graphSettings: this._graphSettingsCache.settings, + }); return; } diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 44bfe04..85c5692 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -272,39 +272,139 @@ class SpanSidePanel extends HTMLElement { } _renderPanelMode(panel) { - const header = this._createHeader(t("sidepanel.panel_monitoring"), t("sidepanel.global_defaults")); + const cfg = this._config; + const header = this._createHeader(t("sidepanel.graph_settings"), t("sidepanel.global_defaults")); panel.appendChild(header); const body = document.createElement("div"); body.className = "panel-body"; - const info = document.createElement("div"); - info.className = "panel-mode-info"; - info.innerHTML = ` -

${t("sidepanel.global_info")}

-

${t("sidepanel.custom_info_prefix")} ${t("sidepanel.custom")} ${t("sidepanel.custom_info_suffix")}

- `; - body.appendChild(info); - - const link = document.createElement("button"); - link.textContent = t("sidepanel.configure_global"); - Object.assign(link.style, { - display: "inline-block", - marginTop: "8px", - padding: "8px 16px", - background: "var(--primary-color, #4dd9af)", - color: "var(--text-primary-color, #000)", - borderRadius: "4px", - border: "none", - cursor: "pointer", - fontSize: "0.85em", - fontWeight: "500", - }); - link.addEventListener("click", () => { - this.close(); - this.dispatchEvent(new CustomEvent("navigate-tab", { detail: "monitoring", bubbles: true, composed: true })); + const errorEl = document.createElement("div"); + errorEl.className = "error-msg"; + errorEl.id = "error-msg"; + errorEl.style.display = "none"; + body.appendChild(errorEl); + + const graphSettings = cfg.graphSettings; + const topology = cfg.topology; + const globalHorizon = graphSettings?.global_horizon ?? DEFAULT_GRAPH_HORIZON; + const circuitSettings = graphSettings?.circuits ?? {}; + + // ── Global default horizon ── + const globalSection = document.createElement("div"); + globalSection.className = "section"; + + const globalLabel = document.createElement("div"); + globalLabel.className = "section-label"; + globalLabel.textContent = t("sidepanel.graph_horizon"); + globalSection.appendChild(globalLabel); + + const globalRow = document.createElement("div"); + globalRow.className = "field-row"; + + const globalFieldLabel = document.createElement("span"); + globalFieldLabel.className = "field-label"; + globalFieldLabel.textContent = t("sidepanel.global_default"); + globalRow.appendChild(globalFieldLabel); + + const globalSelect = document.createElement("select"); + for (const key of Object.keys(GRAPH_HORIZONS)) { + const opt = document.createElement("option"); + opt.value = key; + opt.textContent = key; + if (key === globalHorizon) opt.selected = true; + globalSelect.appendChild(opt); + } + globalSelect.addEventListener("change", () => { + this._callDomainService("set_graph_time_horizon", { horizon: globalSelect.value }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${err.message ?? err}`)); }); - body.appendChild(link); + globalRow.appendChild(globalSelect); + globalSection.appendChild(globalRow); + body.appendChild(globalSection); + + // ── Per-circuit horizon scales ── + if (topology?.circuits) { + const circuitSection = document.createElement("div"); + circuitSection.className = "section"; + + const circuitLabel = document.createElement("div"); + circuitLabel.className = "section-label"; + circuitLabel.textContent = t("sidepanel.circuit_scales"); + circuitSection.appendChild(circuitLabel); + + const circuits = Object.entries(topology.circuits).sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || "")); + + for (const [uuid, circuit] of circuits) { + const row = document.createElement("div"); + row.className = "field-row"; + + const nameLabel = document.createElement("span"); + nameLabel.className = "field-label"; + nameLabel.textContent = circuit.name || uuid; + nameLabel.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;"; + row.appendChild(nameLabel); + + const circuitData = circuitSettings[uuid] || {}; + const effectiveHorizon = circuitData.has_override ? circuitData.horizon : globalHorizon; + + const select = document.createElement("select"); + select.dataset.uuid = uuid; + for (const key of Object.keys(GRAPH_HORIZONS)) { + const opt = document.createElement("option"); + opt.value = key; + opt.textContent = key; + if (key === effectiveHorizon) opt.selected = true; + select.appendChild(opt); + } + select.addEventListener("change", () => { + this._debounce(`circuit-${uuid}`, DEBOUNCE_MS, () => { + this._callDomainService("set_circuit_graph_horizon", { + circuit_id: uuid, + horizon: select.value, + }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${err.message ?? err}`)); + }); + }); + row.appendChild(select); + + if (circuitData.has_override) { + const resetBtn = document.createElement("button"); + resetBtn.textContent = "\u21ba"; + resetBtn.title = t("sidepanel.reset_to_global"); + Object.assign(resetBtn.style, { + background: "none", + border: "1px solid var(--divider-color, #e0e0e0)", + color: "var(--primary-text-color)", + borderRadius: "4px", + padding: "3px 6px", + cursor: "pointer", + marginLeft: "4px", + fontSize: "0.85em", + }); + resetBtn.addEventListener("click", () => { + this._callDomainService("clear_circuit_graph_horizon", { circuit_id: uuid }) + .then(() => { + select.value = globalHorizon; + resetBtn.remove(); + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${err.message ?? err}`)); + }); + row.appendChild(resetBtn); + } + + circuitSection.appendChild(row); + } + + body.appendChild(circuitSection); + } panel.appendChild(body); } diff --git a/src/i18n.js b/src/i18n.js index 76f32f2..a5615a4 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -111,13 +111,11 @@ const translations = { "subdevice.power": "Power", // Side panel - "sidepanel.panel_monitoring": "Panel Monitoring", + "sidepanel.graph_settings": "Graph Settings", "sidepanel.global_defaults": "Global defaults for all circuits", - "sidepanel.global_info": - "Global monitoring thresholds apply to all circuits that don't have custom overrides. Use the integration's options flow to change global settings.", - "sidepanel.custom_info_prefix": "Individual circuit thresholds can be configured by clicking the gear icon on a circuit row and switching to", - "sidepanel.custom_info_suffix": "mode.", - "sidepanel.configure_global": "Configure Global Thresholds", + "sidepanel.global_default": "Global Default", + "sidepanel.circuit_scales": "Circuit Graph Scales", + "sidepanel.reset_to_global": "Reset to global default", "sidepanel.relay": "Relay", "sidepanel.breaker": "Breaker", "sidepanel.relay_failed": "Relay toggle failed:", @@ -261,14 +259,11 @@ const translations = { "subdevice.soc": "SoC", "subdevice.soe": "SoE", "subdevice.power": "Potencia", - "sidepanel.panel_monitoring": "Monitoreo del Panel", + "sidepanel.graph_settings": "Configuraci\u00f3n de Gr\u00e1ficos", "sidepanel.global_defaults": "Valores predeterminados globales para todos los circuitos", - "sidepanel.global_info": - "Los umbrales de monitoreo global se aplican a todos los circuitos sin anulaciones personalizadas. Use el flujo de opciones de la integraci\u00f3n para cambiar la configuraci\u00f3n global.", - "sidepanel.custom_info_prefix": - "Los umbrales individuales de circuito se pueden configurar haciendo clic en el icono de engranaje en una fila de circuito y cambiando al modo", - "sidepanel.custom_info_suffix": ".", - "sidepanel.configure_global": "Configurar Umbrales Globales", + "sidepanel.global_default": "Predeterminado Global", + "sidepanel.circuit_scales": "Escalas de Gr\u00e1ficos de Circuitos", + "sidepanel.reset_to_global": "Restablecer al valor global", "sidepanel.relay": "Rel\u00e9", "sidepanel.breaker": "Interruptor", "sidepanel.relay_failed": "Error al cambiar rel\u00e9:", @@ -404,14 +399,11 @@ const translations = { "subdevice.soc": "SoC", "subdevice.soe": "SoE", "subdevice.power": "Puissance", - "sidepanel.panel_monitoring": "Surveillance du Panneau", + "sidepanel.graph_settings": "Param\u00e8tres des Graphiques", "sidepanel.global_defaults": "Valeurs par d\u00e9faut globales pour tous les circuits", - "sidepanel.global_info": - "Les seuils de surveillance globaux s'appliquent \u00e0 tous les circuits sans remplacement personnalis\u00e9. Utilisez le flux d'options de l'int\u00e9gration pour modifier les param\u00e8tres globaux.", - "sidepanel.custom_info_prefix": - "Les seuils individuels de circuit peuvent \u00eatre configur\u00e9s en cliquant sur l'ic\u00f4ne d'engrenage d'une rang\u00e9e de circuit et en passant au mode", - "sidepanel.custom_info_suffix": ".", - "sidepanel.configure_global": "Configurer les Seuils Globaux", + "sidepanel.global_default": "D\u00e9faut Global", + "sidepanel.circuit_scales": "\u00c9chelles des Graphiques de Circuits", + "sidepanel.reset_to_global": "R\u00e9initialiser \u00e0 la valeur globale", "sidepanel.relay": "Relais", "sidepanel.breaker": "Disjoncteur", "sidepanel.relay_failed": "\u00c9chec du basculement du relais :", @@ -549,14 +541,11 @@ const translations = { "subdevice.soc": "SoC", "subdevice.soe": "SoE", "subdevice.power": "\u96fb\u529b", - "sidepanel.panel_monitoring": "\u30d1\u30cd\u30eb\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0", + "sidepanel.graph_settings": "\u30b0\u30e9\u30d5\u8a2d\u5b9a", "sidepanel.global_defaults": "\u5168\u56de\u8def\u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30c7\u30d5\u30a9\u30eb\u30c8", - "sidepanel.global_info": - "\u30b0\u30ed\u30fc\u30d0\u30eb\u76e3\u8996\u30b7\u304d\u3044\u5024\u306f\u30ab\u30b9\u30bf\u30e0\u4e0a\u66f8\u304d\u306e\u306a\u3044\u5168\u56de\u8def\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002\u7d71\u5408\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u30d5\u30ed\u30fc\u3067\u30b0\u30ed\u30fc\u30d0\u30eb\u8a2d\u5b9a\u3092\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "sidepanel.custom_info_prefix": - "\u500b\u5225\u306e\u56de\u8def\u3057\u304d\u3044\u5024\u306f\u3001\u56de\u8def\u884c\u306e\u6b6f\u8eca\u30a2\u30a4\u30b3\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30e2\u30fc\u30c9\u3092\u5207\u308a\u66ff\u3048\u3066\u8a2d\u5b9a\u3067\u304d\u307e\u3059", - "sidepanel.custom_info_suffix": "\u3002", - "sidepanel.configure_global": "\u30b0\u30ed\u30fc\u30d0\u30eb\u3057\u304d\u3044\u5024\u3092\u8a2d\u5b9a", + "sidepanel.global_default": "\u30b0\u30ed\u30fc\u30d0\u30eb\u30c7\u30d5\u30a9\u30eb\u30c8", + "sidepanel.circuit_scales": "\u56de\u8def\u30b0\u30e9\u30d5\u30b9\u30b1\u30fc\u30eb", + "sidepanel.reset_to_global": "\u30b0\u30ed\u30fc\u30d0\u30eb\u306b\u30ea\u30bb\u30c3\u30c8", "sidepanel.relay": "\u30ea\u30ec\u30fc", "sidepanel.breaker": "\u30d6\u30ec\u30fc\u30ab\u30fc", "sidepanel.relay_failed": "\u30ea\u30ec\u30fc\u5207\u308a\u66ff\u3048\u5931\u6557:", @@ -696,14 +685,11 @@ const translations = { "subdevice.soc": "SoC", "subdevice.soe": "SoE", "subdevice.power": "Pot\u00eancia", - "sidepanel.panel_monitoring": "Monitoramento do Painel", + "sidepanel.graph_settings": "Configura\u00e7\u00f5es de Gr\u00e1ficos", "sidepanel.global_defaults": "Padr\u00f5es globais para todos os circuitos", - "sidepanel.global_info": - "Os limites de monitoramento global se aplicam a todos os circuitos sem substitui\u00e7\u00f5es personalizadas. Use o fluxo de op\u00e7\u00f5es da integra\u00e7\u00e3o para alterar as configura\u00e7\u00f5es globais.", - "sidepanel.custom_info_prefix": - "Os limites individuais de circuito podem ser configurados clicando no \u00edcone de engrenagem em uma linha de circuito e alternando para o modo", - "sidepanel.custom_info_suffix": ".", - "sidepanel.configure_global": "Configurar Limites Globais", + "sidepanel.global_default": "Padr\u00e3o Global", + "sidepanel.circuit_scales": "Escalas de Gr\u00e1ficos de Circuitos", + "sidepanel.reset_to_global": "Redefinir para o padr\u00e3o global", "sidepanel.relay": "Rel\u00e9", "sidepanel.breaker": "Disjuntor", "sidepanel.relay_failed": "Falha ao alternar rel\u00e9:", diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index a44f28f..6b56132 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -293,7 +293,12 @@ export class DashboardTab { sidePanel.hass = this._hass; if (gearBtn.classList.contains("panel-gear")) { - container.dispatchEvent(new CustomEvent("navigate-tab", { detail: "monitoring", bubbles: true, composed: true })); + await this._graphSettingsCache.fetch(this._hass); + sidePanel.open({ + panelMode: true, + topology, + graphSettings: this._graphSettingsCache.settings, + }); return; } From 52401016c906ed39925a9165fb6f2cc1cbd2a78c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:35:50 -0700 Subject: [PATCH 059/101] fix: remove 900px max-width cap so frontend panel fills browser width --- dist/span-panel.js | 2 +- src/panel/span-panel.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index a14c792..d9eb8b6 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=H(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,f,v].filter(Boolean))),y=O(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${v?`
${v}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=H(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,f,v].filter(Boolean))),y=O(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${v?`
${v}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 99bb306..517e534 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -67,13 +67,10 @@ const PANEL_STYLES = ` border-bottom-color: var(--app-header-text-color, white); } .view { - display: flex; - justify-content: center; padding: 16px; } .view-content { width: 100%; - max-width: 900px; } .tab-content { min-height: 400px; From 2042e018ea725bfa22d440c54ffea41640d7c0e7 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:43:14 -0700 Subject: [PATCH 060/101] fix: add 4:1 aspect ratio to chart containers for proportional scaling --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 7f75a37..aca3a22 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(u).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=t("sidepanel.global_default"),g.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=h.power;n.endsWith("_soc")?a=h.soc:n.endsWith("_soe")&&(a=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),h=p?n.states[p]:null,u=h&&parseFloat(h.state)||0,g=d.type===c,f=g?G(d):null,_=g?O(d):null,v=g?q(d):null,b=W(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${z(u)} ${E(u)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(u).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=t("sidepanel.global_default"),g.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=h.power;n.endsWith("_soc")?a=h.soc:n.endsWith("_soe")&&(a=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),h=p?n.states[p]:null,u=h&&parseFloat(h.state)||0,g=d.type===c,f=g?G(d):null,_=g?O(d):null,v=g?q(d):null,b=W(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${z(u)} ${E(u)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index d9eb8b6..5cc57e1 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=H(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,f,v].filter(Boolean))),y=O(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${v?`
${v}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=H(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,f,v].filter(Boolean))),y=O(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${v?`
${v}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index d2ac24a..b7f68ae 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -352,6 +352,7 @@ export const CARD_STYLES = ` .chart-container { width: 100%; + aspect-ratio: 4 / 1; margin-top: 4px; } From cb6b1b85c5b3b043aad9fcef158fd13cd54f9e5a Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:49:36 -0700 Subject: [PATCH 061/101] feat: move sub-devices above circuits with 2-column EVSE grid layout Battery and EVSE sections now render above the circuit grid. EVSEs use a 2-column grid with max two per row; odd-count last EVSE spans full width. Battery always spans full width. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 14 ++++++++++---- src/card/span-panel-card.js | 2 +- src/core/sub-device-renderer.js | 32 ++++++++++++++++++++++++++------ src/panel/tab-dashboard.js | 2 +- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index aca3a22..f9004a7 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(u).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=t("sidepanel.global_default"),g.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=h.power;n.endsWith("_soc")?a=h.soc:n.endsWith("_soe")&&(a=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;let a="";if(!e.sub_devices)return a;for(const[r,d]of Object.entries(e.sub_devices)){if(d.type===c&&!o)continue;if(d.type===l&&!s)continue;const e=d.type===l?t("subdevice.ev_charger"):d.type===c?t("subdevice.battery"):t("subdevice.fallback"),p=j(d),h=p?n.states[p]:null,u=h&&parseFloat(h.state)||0,g=d.type===c,f=g?G(d):null,_=g?O(d):null,v=g?q(d):null,b=W(d,n,i,new Set([p,f,_,v].filter(Boolean))),y=V(r,0,g,p,f,_);a+=`\n
\n
\n ${m(e)}\n ${m(d.name||"")}\n ${p?`${z(u)} ${E(u)}`:""}\n
\n ${y}\n ${b}\n
\n `}return a}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n ${d?`
${d}
`:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(u).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=t("sidepanel.global_default"),g.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=h.power;n.endsWith("_soc")?a=h.soc:n.endsWith("_soe")&&(a=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,p="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=j(o),h=a?n.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===c,f=o.type===l,_=g?G(o):null,v=g?O(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,_,v,b].filter(Boolean))),w=V(e,0,g,a,_,v);let x="";g?x="sub-device-bess":f&&(d++,d===r&&r%2==1&&(x="sub-device-full")),p+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${z(u)} ${E(u)}`:""}\n
\n ${w}\n ${y}\n
\n `}return p}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 5cc57e1..068aac2 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;let s="";if(!e.sub_devices)return s;for(const[r,l]of Object.entries(e.sub_devices)){if(l.type===c&&!o)continue;if(l.type===d&&!a)continue;const e=l.type===d?n("subdevice.ev_charger"):l.type===c?n("subdevice.battery"):n("subdevice.fallback"),p=H(l),u=p?t.states[p]:null,h=u&&parseFloat(u.state)||0,g=l.type===c,m=g?R(l):null,f=g?j(l):null,v=g?F(l):null,b=G(l,t,i,new Set([p,m,f,v].filter(Boolean))),y=O(r,0,g,p,m,f);s+=`\n
\n
\n ${_(e)}\n ${_(l.name||"")}\n ${p?`${$(h)} ${w(h)}`:""}\n
\n ${y}\n ${b}\n
\n `}return s}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n ${v?`
${v}
`:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;if(!e.sub_devices)return"";const s=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===d&&!a));if(0===s.length)return"";const r=s.filter(([,e])=>e.type===d).length;let l=0,p="";for(const[e,o]of s){const a=o.type===d?n("subdevice.ev_charger"):o.type===c?n("subdevice.battery"):n("subdevice.fallback"),s=H(o),u=s?t.states[s]:null,h=u&&parseFloat(u.state)||0,g=o.type===c,m=o.type===d,f=g?R(o):null,v=g?j(o):null,b=g?F(o):null,y=G(o,t,i,new Set([s,f,v,b].filter(Boolean))),x=O(e,0,g,s,f,v);let S="";g?S="sub-device-bess":m&&(l++,l===r&&r%2==1&&(S="sub-device-full")),p+=`\n
\n
\n ${_(a)}\n ${_(o.name||"")}\n ${s?`${$(h)} ${w(h)}`:""}\n
\n ${x}\n ${y}\n
\n `}return p}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${v?`
${v}
`:""}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index b7f68ae..c303c4e 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -357,18 +357,24 @@ export const CARD_STYLES = ` } .sub-devices { - margin-top: 20px; - padding-top: 16px; - border-top: 1px solid var(--divider-color, #333); + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid var(--divider-color, #333); } .sub-device { - margin-bottom: 12px; background: var(--secondary-background-color, var(--card-background-color, #2a2a2a)); border: 1px solid var(--divider-color, #333); border-radius: 12px; padding: 14px 16px; } + .sub-device-bess, + .sub-device-full { + grid-column: 1 / -1; + } .sub-device-header { display: flex; gap: 10px; align-items: baseline; margin-bottom: 8px; } .sub-device-type { font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--span-accent); } diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 2ada500..4c0f72b 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -493,6 +493,7 @@ export class SpanPanelCard extends HTMLElement { ${headerHTML} ${monitoringSummaryHTML} + ${subDevHTML ? `
${subDevHTML}
` : ""} ${ this._config.show_panel !== false ? ` @@ -502,7 +503,6 @@ export class SpanPanelCard extends HTMLElement { ` : "" } - ${subDevHTML ? `
${subDevHTML}
` : ""}
`; diff --git a/src/core/sub-device-renderer.js b/src/core/sub-device-renderer.js index ddc53c4..e9bd312 100644 --- a/src/core/sub-device-renderer.js +++ b/src/core/sub-device-renderer.js @@ -16,14 +16,22 @@ import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_TYPE_EVSE, SUB_DEVICE_KEY_PREFIX } fro export function buildSubDevicesHTML(topology, hass, config, _durationMs) { const showBattery = config.show_battery !== false; const showEvse = config.show_evse !== false; - let subDevHTML = ""; - if (!topology.sub_devices) return subDevHTML; + if (!topology.sub_devices) return ""; + + const entries = Object.entries(topology.sub_devices).filter(([, sub]) => { + if (sub.type === SUB_DEVICE_TYPE_BESS && !showBattery) return false; + if (sub.type === SUB_DEVICE_TYPE_EVSE && !showEvse) return false; + return true; + }); + + if (entries.length === 0) return ""; - for (const [devId, sub] of Object.entries(topology.sub_devices)) { - if (sub.type === SUB_DEVICE_TYPE_BESS && !showBattery) continue; - if (sub.type === SUB_DEVICE_TYPE_EVSE && !showEvse) continue; + const evseCount = entries.filter(([, sub]) => sub.type === SUB_DEVICE_TYPE_EVSE).length; + let evseIndex = 0; + let subDevHTML = ""; + for (const [devId, sub] of entries) { const label = sub.type === SUB_DEVICE_TYPE_EVSE ? t("subdevice.ev_charger") : sub.type === SUB_DEVICE_TYPE_BESS ? t("subdevice.battery") : t("subdevice.fallback"); const powerEid = findSubDevicePowerEntity(sub); @@ -31,6 +39,7 @@ export function buildSubDevicesHTML(topology, hass, config, _durationMs) { const powerW = powerState ? parseFloat(powerState.state) || 0 : 0; const isBess = sub.type === SUB_DEVICE_TYPE_BESS; + const isEvse = sub.type === SUB_DEVICE_TYPE_EVSE; const battLevelEid = isBess ? findBatteryLevelEntity(sub) : null; const battSoeEid = isBess ? findBatterySoeEntity(sub) : null; const battCapEid = isBess ? findBatteryCapacityEntity(sub) : null; @@ -39,8 +48,19 @@ export function buildSubDevicesHTML(topology, hass, config, _durationMs) { const entHTML = buildSubEntityHTML(sub, hass, config, hideEids); const chartsHTML = buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid); + // EVSE: span full row if it's the odd one out (last on its row alone) + let spanClass = ""; + if (isBess) { + spanClass = "sub-device-bess"; + } else if (isEvse) { + evseIndex++; + if (evseIndex === evseCount && evseCount % 2 === 1) { + spanClass = "sub-device-full"; + } + } + subDevHTML += ` -
+
${escapeHtml(label)} ${escapeHtml(sub.name || "")} diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 6b56132..2529eff 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -69,6 +69,7 @@ export class DashboardTab { ${headerHTML} ${monitoringSummaryHTML} + ${subDevHTML ? `
${subDevHTML}
` : ""} ${ config.show_panel !== false ? ` @@ -78,7 +79,6 @@ export class DashboardTab { ` : "" } - ${subDevHTML ? `
${subDevHTML}
` : ""} `; From 1c4e262382915a1ac9b17a25c9e09bdc0f0c10bb Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:13:51 -0700 Subject: [PATCH 062/101] feat: add getEffectiveSubDeviceHorizon to graph settings --- src/core/graph-settings.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/core/graph-settings.js b/src/core/graph-settings.js index 2081289..ccc6bb1 100644 --- a/src/core/graph-settings.js +++ b/src/core/graph-settings.js @@ -77,3 +77,16 @@ export function getEffectiveHorizon(settings, circuitId) { if (override?.has_override) return override.horizon; return settings.global_horizon || DEFAULT_GRAPH_HORIZON; } + +/** + * Get the effective horizon for a sub-device. + * @param {object|null} settings - Full graph settings from get_graph_settings + * @param {string} subDeviceId - Sub-device identifier + * @returns {string} Horizon key (e.g., "5m", "1h") + */ +export function getEffectiveSubDeviceHorizon(settings, subDeviceId) { + if (!settings) return DEFAULT_GRAPH_HORIZON; + const override = settings.sub_devices?.[subDeviceId]; + if (override?.has_override) return override.horizon; + return settings.global_horizon || DEFAULT_GRAPH_HORIZON; +} From 31396b0400c314b2a2aab1036660f82f42d8e08c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:14:22 -0700 Subject: [PATCH 063/101] feat: group sub-device history loading by per-device horizon --- src/core/history-loader.js | 40 +++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/core/history-loader.js b/src/core/history-loader.js index f5e3302..c430b5a 100644 --- a/src/core/history-loader.js +++ b/src/core/history-loader.js @@ -72,26 +72,12 @@ async function loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHi } } -/** - * Collect entity IDs for sub-devices into the provided arrays. - * - * @param {object} topology - * @param {string[]} entityIds - mutated in place - * @param {Map} uuidByEntity - mutated in place - */ -function _collectSubDeviceEntityIdsInto(topology, entityIds, uuidByEntity) { - for (const { entityId, key } of collectSubDeviceEntityIds(topology)) { - entityIds.push(entityId); - uuidByEntity.set(entityId, key); - } -} - /** * Build the entity ID list for all sub-devices. * Returns an array of { entityId, key } pairs so callers can record live samples. * * @param {object} topology - * @returns {{ entityId: string, key: string }[]} + * @returns {{ entityId: string, key: string, devId: string }[]} */ export function collectSubDeviceEntityIds(topology) { if (!topology.sub_devices) return []; @@ -104,7 +90,7 @@ export function collectSubDeviceEntityIds(topology) { } for (const [role, eid] of Object.entries(eidMap)) { if (eid) { - results.push({ entityId: eid, key: `${SUB_DEVICE_KEY_PREFIX}${devId}_${role}` }); + results.push({ entityId: eid, key: `${SUB_DEVICE_KEY_PREFIX}${devId}_${role}`, devId }); } } } @@ -120,8 +106,9 @@ export function collectSubDeviceEntityIds(topology) { * @param {object} config - card config (fallback for duration) * @param {Map} powerHistory - mutated in place * @param {Map} [horizonMap] - optional uuid → horizon key map + * @param {Map} [subDeviceHorizonMap] - optional devId → horizon key map */ -export async function loadHistory(hass, topology, config, powerHistory, horizonMap) { +export async function loadHistory(hass, topology, config, powerHistory, horizonMap, subDeviceHorizonMap) { if (!topology || !hass) return; // Group circuits by effective duration @@ -146,12 +133,21 @@ export async function loadHistory(hass, topology, config, powerHistory, horizonM group.uuidByEntity.set(eid, uuid); } - // Add sub-device entities to the default duration group - const defaultDurationMs = getHistoryDurationMs(config); - if (!groups.has(defaultDurationMs)) { - groups.set(defaultDurationMs, { entityIds: [], uuidByEntity: new Map() }); + // Add sub-device entities grouped by their effective horizon + for (const { entityId, key, devId } of collectSubDeviceEntityIds(topology)) { + let durationMs; + if (subDeviceHorizonMap && subDeviceHorizonMap.has(devId)) { + durationMs = getHorizonDurationMs(subDeviceHorizonMap.get(devId)); + } else { + durationMs = getHistoryDurationMs(config); + } + if (!groups.has(durationMs)) { + groups.set(durationMs, { entityIds: [], uuidByEntity: new Map() }); + } + const group = groups.get(durationMs); + group.entityIds.push(entityId); + group.uuidByEntity.set(entityId, key); } - _collectSubDeviceEntityIdsInto(topology, groups.get(defaultDurationMs).entityIds, groups.get(defaultDurationMs).uuidByEntity); // Load each group in parallel const promises = []; From 6c7c7f37028950835e6c752911bbebb648097ac2 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:14:39 -0700 Subject: [PATCH 064/101] feat: use per-sub-device horizons in chart rendering --- src/core/dom-updater.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/dom-updater.js b/src/core/dom-updater.js index 4464c57..aa17676 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.js @@ -209,9 +209,9 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor } } -export function updateSubDeviceDOM(root, hass, topology, config, powerHistory) { +export function updateSubDeviceDOM(root, hass, topology, config, powerHistory, subDeviceHorizonMap) { if (!topology.sub_devices) return; - const durationMs = getHistoryDurationMs(config); + const defaultDurationMs = getHistoryDurationMs(config); for (const [devId, sub] of Object.entries(topology.sub_devices)) { const section = root.querySelector(`[data-subdev="${devId}"]`); @@ -235,7 +235,8 @@ export function updateSubDeviceDOM(root, hass, topology, config, powerHistory) { if (chartKey.endsWith("_soc")) metric = BESS_CHART_METRICS.soc; else if (chartKey.endsWith("_soe")) metric = BESS_CHART_METRICS.soe; const isBessCol = !!cc.closest(".bess-chart-col"); - updateChart(cc, hass, history, durationMs, metric, false, isBessCol ? 120 : 150); + const devDuration = subDeviceHorizonMap?.has(devId) ? getHorizonDurationMs(subDeviceHorizonMap.get(devId)) : defaultDurationMs; + updateChart(cc, hass, history, devDuration, metric, false, isBessCol ? 120 : 150); } for (const entityId of Object.keys(sub.entities || {})) { From a0d24e0677d5b453676c020e77c9fa0e7259d0e3 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:14:55 -0700 Subject: [PATCH 065/101] feat: add gear icon to sub-device headers --- src/core/sub-device-renderer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/sub-device-renderer.js b/src/core/sub-device-renderer.js index e9bd312..00cc1d7 100644 --- a/src/core/sub-device-renderer.js +++ b/src/core/sub-device-renderer.js @@ -65,6 +65,9 @@ export function buildSubDevicesHTML(topology, hass, config, _durationMs) { ${escapeHtml(label)} ${escapeHtml(sub.name || "")} ${powerEid ? `${formatPowerSigned(powerW)} ${formatPowerUnit(powerW)}` : ""} +
${chartsHTML} ${entHTML} From aeacdbde6ec4423b506e032b5dd5416288f8b84e Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:19:33 -0700 Subject: [PATCH 066/101] feat: add sub-device mode to side panel with horizon bar --- src/core/side-panel.js | 172 +++++++++++++++++++++++++++++++++++++++++ src/i18n.js | 5 ++ 2 files changed, 177 insertions(+) diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 85c5692..04a399a 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -266,6 +266,8 @@ class SpanSidePanel extends HTMLElement { if (cfg.panelMode) { this._renderPanelMode(panel); + } else if (cfg.subDeviceMode) { + this._renderSubDeviceMode(panel, cfg); } else { this._renderCircuitMode(panel, cfg); } @@ -406,6 +408,87 @@ class SpanSidePanel extends HTMLElement { body.appendChild(circuitSection); } + // ── Per-sub-device horizon scales ── + const subDeviceSettings = graphSettings?.sub_devices ?? {}; + if (topology?.sub_devices) { + const subDevSection = document.createElement("div"); + subDevSection.className = "section"; + + const subDevLabel = document.createElement("div"); + subDevLabel.className = "section-label"; + subDevLabel.textContent = t("sidepanel.subdevice_scales"); + subDevSection.appendChild(subDevLabel); + + const subDevices = Object.entries(topology.sub_devices).sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || "")); + + for (const [devId, sub] of subDevices) { + const row = document.createElement("div"); + row.className = "field-row"; + + const nameLabel = document.createElement("span"); + nameLabel.className = "field-label"; + nameLabel.textContent = sub.name || devId; + nameLabel.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;"; + row.appendChild(nameLabel); + + const subDevData = subDeviceSettings[devId] || {}; + const effectiveHorizon = subDevData.has_override ? subDevData.horizon : globalHorizon; + + const select = document.createElement("select"); + select.dataset.subdevId = devId; + for (const key of Object.keys(GRAPH_HORIZONS)) { + const opt = document.createElement("option"); + opt.value = key; + opt.textContent = key; + if (key === effectiveHorizon) opt.selected = true; + select.appendChild(opt); + } + select.addEventListener("change", () => { + this._debounce(`subdev-${devId}`, DEBOUNCE_MS, () => { + this._callDomainService("set_subdevice_graph_horizon", { + subdevice_id: devId, + horizon: select.value, + }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${err.message ?? err}`)); + }); + }); + row.appendChild(select); + + if (subDevData.has_override) { + const resetBtn = document.createElement("button"); + resetBtn.textContent = "\u21ba"; + resetBtn.title = t("sidepanel.reset_to_global"); + Object.assign(resetBtn.style, { + background: "none", + border: "1px solid var(--divider-color, #e0e0e0)", + color: "var(--primary-text-color)", + borderRadius: "4px", + padding: "3px 6px", + cursor: "pointer", + marginLeft: "4px", + fontSize: "0.85em", + }); + resetBtn.addEventListener("click", () => { + this._callDomainService("clear_subdevice_graph_horizon", { subdevice_id: devId }) + .then(() => { + select.value = globalHorizon; + resetBtn.remove(); + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${err.message ?? err}`)); + }); + row.appendChild(resetBtn); + } + + subDevSection.appendChild(row); + } + + body.appendChild(subDevSection); + } + panel.appendChild(body); } @@ -430,6 +513,95 @@ class SpanSidePanel extends HTMLElement { this._renderMonitoringSection(body, cfg); } + _renderSubDeviceMode(panel, cfg) { + const header = this._createHeader(escapeHtml(cfg.name), escapeHtml(cfg.deviceType)); + panel.appendChild(header); + + const body = document.createElement("div"); + body.className = "panel-body"; + panel.appendChild(body); + + const errorEl = document.createElement("div"); + errorEl.className = "error-msg"; + errorEl.id = "error-msg"; + errorEl.style.display = "none"; + body.appendChild(errorEl); + + this._renderSubDeviceHorizonSection(body, cfg); + } + + _renderSubDeviceHorizonSection(body, cfg) { + const section = document.createElement("div"); + section.className = "section"; + + const sectionLabel = document.createElement("div"); + sectionLabel.className = "section-label"; + sectionLabel.textContent = t("sidepanel.graph_horizon"); + section.appendChild(sectionLabel); + + const graphInfo = cfg.graphHorizonInfo; + const hasOverride = graphInfo?.has_override === true; + const currentHorizon = graphInfo?.horizon || DEFAULT_GRAPH_HORIZON; + const globalHorizon = graphInfo?.globalHorizon || DEFAULT_GRAPH_HORIZON; + + const bar = document.createElement("div"); + bar.className = "horizon-bar"; + + const segments = [{ key: "global", label: t("sidepanel.global") }]; + for (const key of Object.keys(GRAPH_HORIZONS)) { + segments.push({ key, label: key }); + } + + const activeKey = hasOverride ? currentHorizon : "global"; + + const updateSegmentStates = newActiveKey => { + for (const btn of bar.querySelectorAll(".horizon-segment")) { + const key = btn.dataset.horizon; + btn.classList.toggle("active", key === newActiveKey); + btn.classList.toggle("referenced", newActiveKey === "global" && key === globalHorizon); + } + }; + + for (const { key, label } of segments) { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "horizon-segment"; + btn.dataset.horizon = key; + btn.textContent = label; + btn.classList.toggle("active", key === activeKey); + btn.classList.toggle("referenced", activeKey === "global" && key === globalHorizon); + + btn.addEventListener("click", () => { + if (btn.classList.contains("active")) return; + + const subDeviceId = cfg.subDeviceId; + if (key === "global") { + updateSegmentStates("global"); + this._callDomainService("clear_subdevice_graph_horizon", { subdevice_id: subDeviceId }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${err.message ?? err}`)); + } else { + updateSegmentStates(key); + this._callDomainService("set_subdevice_graph_horizon", { + subdevice_id: subDeviceId, + horizon: key, + }) + .then(() => { + this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); + }) + .catch(err => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); + } + }); + + bar.appendChild(btn); + } + + section.appendChild(bar); + body.appendChild(section); + } + _createHeader(title, subtitle) { const header = document.createElement("div"); header.className = "panel-header"; diff --git a/src/i18n.js b/src/i18n.js index a5615a4..b694b4e 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -115,6 +115,7 @@ const translations = { "sidepanel.global_defaults": "Global defaults for all circuits", "sidepanel.global_default": "Global Default", "sidepanel.circuit_scales": "Circuit Graph Scales", + "sidepanel.subdevice_scales": "Sub-Device Graph Scales", "sidepanel.reset_to_global": "Reset to global default", "sidepanel.relay": "Relay", "sidepanel.breaker": "Breaker", @@ -263,6 +264,7 @@ const translations = { "sidepanel.global_defaults": "Valores predeterminados globales para todos los circuitos", "sidepanel.global_default": "Predeterminado Global", "sidepanel.circuit_scales": "Escalas de Gr\u00e1ficos de Circuitos", + "sidepanel.subdevice_scales": "Escalas de Gr\u00e1ficos de Sub-Dispositivos", "sidepanel.reset_to_global": "Restablecer al valor global", "sidepanel.relay": "Rel\u00e9", "sidepanel.breaker": "Interruptor", @@ -403,6 +405,7 @@ const translations = { "sidepanel.global_defaults": "Valeurs par d\u00e9faut globales pour tous les circuits", "sidepanel.global_default": "D\u00e9faut Global", "sidepanel.circuit_scales": "\u00c9chelles des Graphiques de Circuits", + "sidepanel.subdevice_scales": "\u00c9chelles des Graphiques de Sous-Appareils", "sidepanel.reset_to_global": "R\u00e9initialiser \u00e0 la valeur globale", "sidepanel.relay": "Relais", "sidepanel.breaker": "Disjoncteur", @@ -545,6 +548,7 @@ const translations = { "sidepanel.global_defaults": "\u5168\u56de\u8def\u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30c7\u30d5\u30a9\u30eb\u30c8", "sidepanel.global_default": "\u30b0\u30ed\u30fc\u30d0\u30eb\u30c7\u30d5\u30a9\u30eb\u30c8", "sidepanel.circuit_scales": "\u56de\u8def\u30b0\u30e9\u30d5\u30b9\u30b1\u30fc\u30eb", + "sidepanel.subdevice_scales": "\u30b5\u30d6\u30c7\u30d0\u30a4\u30b9\u30b0\u30e9\u30d5\u30b9\u30b1\u30fc\u30eb", "sidepanel.reset_to_global": "\u30b0\u30ed\u30fc\u30d0\u30eb\u306b\u30ea\u30bb\u30c3\u30c8", "sidepanel.relay": "\u30ea\u30ec\u30fc", "sidepanel.breaker": "\u30d6\u30ec\u30fc\u30ab\u30fc", @@ -689,6 +693,7 @@ const translations = { "sidepanel.global_defaults": "Padr\u00f5es globais para todos os circuitos", "sidepanel.global_default": "Padr\u00e3o Global", "sidepanel.circuit_scales": "Escalas de Gr\u00e1ficos de Circuitos", + "sidepanel.subdevice_scales": "Escalas de Gr\u00e1ficos de Sub-Dispositivos", "sidepanel.reset_to_global": "Redefinir para o padr\u00e3o global", "sidepanel.relay": "Rel\u00e9", "sidepanel.breaker": "Disjuntor", From 4b78bdc945629c91a85f0b4117ca371f1b460341 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:22:15 -0700 Subject: [PATCH 067/101] feat: wire sub-device horizon map through card lifecycle --- src/card/span-panel-card.js | 93 +++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 25 deletions(-) diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 4c0f72b..53d75fc 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -41,6 +41,7 @@ export class SpanPanelCard extends HTMLElement { this._monitoringCache = new MonitoringStatusCache(); this._graphSettingsCache = new GraphSettingsCache(); this._horizonMap = new Map(); + this._subDeviceHorizonMap = new Map(); } connectedCallback() { @@ -114,6 +115,7 @@ export class SpanPanelCard extends HTMLElement { this._historyLoaded = false; this._powerHistory.clear(); this._horizonMap.clear(); + this._subDeviceHorizonMap.clear(); this._monitoringCache.clear(); this._graphSettingsCache.clear(); } @@ -213,12 +215,20 @@ export class SpanPanelCard extends HTMLElement { this._horizonMap.set(uuid, horizon); } } + // Build sub-device horizon map + if (settings && this._topology?.sub_devices) { + for (const devId of Object.keys(this._topology.sub_devices)) { + const override = settings.sub_devices?.[devId]; + const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; + this._subDeviceHorizonMap.set(devId, horizon); + } + } } catch { // Graph settings unavailable — use defaults } try { - await loadHistory(this._hass, this._topology, this._config, this._powerHistory, this._horizonMap); + await loadHistory(this._hass, this._topology, this._config, this._powerHistory, this._horizonMap, this._subDeviceHorizonMap); this._updateDOM(); } catch (err) { console.warn("SPAN Panel: history fetch failed, charts will populate live", err); @@ -246,14 +256,17 @@ export class SpanPanelCard extends HTMLElement { recordSample(this._powerHistory, uuid, rawValue, now, cutoff, maxPoints); } - // Sub-devices always use default duration - const defaultDurationMs = getHistoryDurationMs(this._config); - const defaultMaxPoints = getMaxHistoryPoints(defaultDurationMs); - const defaultCutoff = now - defaultDurationMs; - for (const { entityId, key } of collectSubDeviceEntityIds(this._topology)) { + // Sub-devices use per-device horizon when available + for (const { entityId, key, devId } of collectSubDeviceEntityIds(this._topology)) { + const horizon = this._subDeviceHorizonMap?.get(devId) || DEFAULT_GRAPH_HORIZON; + if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; + const state = this._hass.states[entityId]; const rawValue = state ? parseFloat(state.state) || 0 : 0; - recordSample(this._powerHistory, key, rawValue, now, defaultCutoff, defaultMaxPoints); + const durationMs = getHorizonDurationMs(horizon); + const maxPoints = getMaxHistoryPoints(durationMs); + const cutoff = now - durationMs; + recordSample(this._powerHistory, key, rawValue, now, cutoff, maxPoints); } } @@ -268,7 +281,7 @@ export class SpanPanelCard extends HTMLElement { _updateDOM() { updateCircuitDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory); + updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory, this._subDeviceHorizonMap); } // ── Unit toggle (A/W) click handler ─────────────────────────────────────── @@ -412,26 +425,47 @@ export class SpanPanelCard extends HTMLElement { } const uuid = gearBtn.dataset.uuid; - if (!uuid || !this._topology) return; + if (uuid && this._topology) { + const circuit = this._topology.circuits[uuid]; + if (circuit) { + const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; + + await this._graphSettingsCache.fetch(this._hass); + const graphSettings = this._graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + const graphHorizonInfo = graphSettings?.circuits?.[uuid] + ? { ...graphSettings.circuits[uuid], globalHorizon } + : { horizon: globalHorizon, has_override: false, globalHorizon }; + + sidePanel.open({ + ...circuit, + uuid, + monitoringInfo, + graphHorizonInfo, + }); + return; + } + } - const circuit = this._topology.circuits[uuid]; - if (!circuit) return; + const subDevId = gearBtn.dataset.subdevId; + if (subDevId && this._topology?.sub_devices?.[subDevId]) { + const sub = this._topology.sub_devices[subDevId]; - const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; + await this._graphSettingsCache.fetch(this._hass); + const graphSettings = this._graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + const graphHorizonInfo = graphSettings?.sub_devices?.[subDevId] + ? { ...graphSettings.sub_devices[subDevId], globalHorizon } + : { horizon: globalHorizon, has_override: false, globalHorizon }; - await this._graphSettingsCache.fetch(this._hass); - const graphSettings = this._graphSettingsCache.settings; - const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - const graphHorizonInfo = graphSettings?.circuits?.[uuid] - ? { ...graphSettings.circuits[uuid], globalHorizon } - : { horizon: globalHorizon, has_override: false, globalHorizon }; - - sidePanel.open({ - ...circuit, - uuid, - monitoringInfo, - graphHorizonInfo, - }); + sidePanel.open({ + subDeviceMode: true, + subDeviceId: subDevId, + name: sub.name || subDevId, + deviceType: sub.type || "", + graphHorizonInfo, + }); + } } // ── Graph settings changed handler ──────────────────────────────────────── @@ -450,6 +484,15 @@ export class SpanPanelCard extends HTMLElement { } } + // Rebuild sub-device horizon map + if (settings && this._topology?.sub_devices) { + for (const devId of Object.keys(this._topology.sub_devices)) { + const override = settings.sub_devices?.[devId]; + const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; + this._subDeviceHorizonMap.set(devId, horizon); + } + } + // Reload all history with new horizons this._powerHistory.clear(); this._historyLoaded = false; From 1674a7b43a7897e1a51043a1cf2c5dd6c8c46110 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:23:49 -0700 Subject: [PATCH 068/101] feat: wire sub-device horizon map through panel dashboard --- src/panel/tab-dashboard.js | 113 ++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 26 deletions(-) diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 2529eff..9134f92 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -3,7 +3,7 @@ import { buildHeaderHTML } from "../core/header-renderer.js"; import { buildGridHTML } from "../core/grid-renderer.js"; import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; -import { loadHistory } from "../core/history-loader.js"; +import { loadHistory, collectSubDeviceEntityIds } from "../core/history-loader.js"; import { MonitoringStatusCache, buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; import { GraphSettingsCache } from "../core/graph-settings.js"; import { CARD_STYLES } from "../card/card-styles.js"; @@ -22,6 +22,7 @@ export class DashboardTab { this._updateInterval = null; this._recorderRefreshInterval = null; this._horizonMap = new Map(); + this._subDeviceHorizonMap = new Map(); this._hass = null; this._config = null; } @@ -56,6 +57,14 @@ export class DashboardTab { this._horizonMap.set(uuid, horizon); } } + // Build sub-device horizon map + if (topo?.sub_devices) { + for (const devId of Object.keys(topo.sub_devices)) { + const override = graphSettings?.sub_devices?.[devId]; + const horizon = override?.has_override ? override.horizon : graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + this._subDeviceHorizonMap.set(devId, horizon); + } + } const totalRows = Math.ceil(this._panelSize / 2); const durationMs = getHistoryDurationMs(config); const monitoringStatus = this._monitoringCache.status; @@ -102,26 +111,35 @@ export class DashboardTab { } } + // Rebuild sub-device horizon map + if (topo?.sub_devices) { + for (const devId of Object.keys(topo.sub_devices)) { + const override = newSettings?.sub_devices?.[devId]; + const horizon = override?.has_override ? override.horizon : newSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + this._subDeviceHorizonMap.set(devId, horizon); + } + } + // Reload all history with new horizons this._powerHistory.clear(); try { - await loadHistory(this._hass, topo, this._config, this._powerHistory, this._horizonMap); + await loadHistory(this._hass, topo, this._config, this._powerHistory, this._horizonMap, this._subDeviceHorizonMap); } catch { // Will populate on next refresh } updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory); + updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory, this._subDeviceHorizonMap); }); try { - await loadHistory(hass, topo, config, this._powerHistory, this._horizonMap); + await loadHistory(hass, topo, config, this._powerHistory, this._horizonMap, this._subDeviceHorizonMap); } catch { // Charts will populate live } // Initial DOM update with history data updateCircuitDOM(container, hass, topo, config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(container, hass, topo, config, this._powerHistory); + updateSubDeviceDOM(container, hass, topo, config, this._powerHistory, this._subDeviceHorizonMap); const slideEl = container.querySelector(".slide-confirm"); if (slideEl) { @@ -133,7 +151,7 @@ export class DashboardTab { this._updateInterval = setInterval(() => { this._recordSamples(); updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory); + updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory, this._subDeviceHorizonMap); }, LIVE_SAMPLE_INTERVAL_MS); // Periodic recorder refresh for non-realtime horizons @@ -150,7 +168,7 @@ export class DashboardTab { // until fresh data is ready (avoids blank-chart flash). const freshHistory = new Map(); try { - await loadHistory(this._hass, this._topology, this._config, freshHistory, nonRealtimeMap); + await loadHistory(this._hass, this._topology, this._config, freshHistory, nonRealtimeMap, this._subDeviceHorizonMap); // Atomically replace only the non-realtime entries for (const uuid of nonRealtimeMap.keys()) { const data = freshHistory.get(uuid); @@ -192,6 +210,27 @@ export class DashboardTab { recordSample(this._powerHistory, uuid, val, now, cutoff, maxPoints); } + + // Sub-device samples with per-device horizons + for (const { entityId, key, devId } of collectSubDeviceEntityIds(this._topology)) { + const horizon = this._subDeviceHorizonMap?.get(devId) || DEFAULT_GRAPH_HORIZON; + if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; + + const state = this._hass.states[entityId]; + if (!state) continue; + const val = parseFloat(state.state); + if (isNaN(val)) continue; + + const durationMs = getHorizonDurationMs(horizon); + const maxPoints = getMaxHistoryPoints(durationMs); + const minGap = getMinGapMs(durationMs); + const cutoff = now - durationMs; + + const hist = this._powerHistory.get(key) || []; + if (hist.length > 0 && now - hist[hist.length - 1].time < minGap) continue; + + recordSample(this._powerHistory, key, val, now, cutoff, maxPoints); + } } _bindSlideConfirm(slideEl, parent) { @@ -303,28 +342,50 @@ export class DashboardTab { } const uuid = gearBtn.dataset.uuid; - if (!uuid || !topology) return; + if (uuid && topology) { + const circuit = topology.circuits[uuid]; + if (circuit) { + // Always fetch fresh monitoring and graph settings data before opening side panel + await this._monitoringCache.fetch(this._hass); + const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; + + await this._graphSettingsCache.fetch(this._hass); + const graphSettings = this._graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + const graphHorizonInfo = graphSettings?.circuits?.[uuid] + ? { ...graphSettings.circuits[uuid], globalHorizon } + : { horizon: globalHorizon, has_override: false, globalHorizon }; + + sidePanel.open({ + ...circuit, + uuid, + monitoringInfo, + graphHorizonInfo, + }); + return; + } + } - const circuit = topology.circuits[uuid]; - if (!circuit) return; + const subDevId = gearBtn.dataset.subdevId; + if (subDevId && topology?.sub_devices?.[subDevId]) { + const sub = topology.sub_devices[subDevId]; - // Always fetch fresh monitoring and graph settings data before opening side panel - await this._monitoringCache.fetch(this._hass); - const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; + await this._graphSettingsCache.fetch(this._hass); + const graphSettings = this._graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; + const graphHorizonInfo = graphSettings?.sub_devices?.[subDevId] + ? { ...graphSettings.sub_devices[subDevId], globalHorizon } + : { horizon: globalHorizon, has_override: false, globalHorizon }; - await this._graphSettingsCache.fetch(this._hass); - const graphSettings = this._graphSettingsCache.settings; - const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - const graphHorizonInfo = graphSettings?.circuits?.[uuid] - ? { ...graphSettings.circuits[uuid], globalHorizon } - : { horizon: globalHorizon, has_override: false, globalHorizon }; - - sidePanel.open({ - ...circuit, - uuid, - monitoringInfo, - graphHorizonInfo, - }); + sidePanel.open({ + subDeviceMode: true, + subDeviceId: subDevId, + name: sub.name || subDevId, + deviceType: sub.type || "", + graphHorizonInfo, + }); + return; + } }); } From 70f152f11d51c45aac6de11e496b283f8b731d7b Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:29:46 -0700 Subject: [PATCH 069/101] feat: update dist with sub-device horizon support --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index f9004a7..0481a1b 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>f[e])}function _(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const k=p.power;function E(e){return k.unit(e)}function z(e){return(e<0?"-":"")+k.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function M(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":M(t)===M(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const f=n.entities?.power,_=f?l.states[f]:null,v=_&&parseFloat(_.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(_?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,k=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),P=x(d);let M;if("current"===P.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;M=`${P.format(i)}A`}else M=`${z(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?g:"#555",D=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${$}\n
\n
\n \n ${M}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${D}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const D={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,D)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`})}return t}async function J(e,t,n,i,o){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let a;a=o&&o.has(e)?v(o.get(e)):_(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const a=_(n);s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map}),function(e,t,n){for(const{entityId:i,key:o}of X(e))t.push(i),n.set(i,o)}(t,s.get(a).entityIds,s.get(a).uuidByEntity);const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):f<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=_(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,f=g?n.states[g]:null,m=f&&parseFloat(f.state)||0,_=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(f?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${z(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",_),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const k=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",k.icon),$.style.color=k.color,$.title=k.label());const P=i.querySelector(".shedding-icon-secondary");P&&(k.icon2?(P.setAttribute("icon",k.icon2),P.style.color=k.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,_,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=Object.keys(u).filter(e=>"unknown"!==e);class ee extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=t("sidepanel.global_default"),g.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of Z){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",f,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ee);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return _(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};f[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:f},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}const t=_(this._config),n=b(t),s=e-t;for(const{entityId:t,key:i}of X(this._topology)){const o=this._hass.states[t],a=o&&parseFloat(o.state)||0;y(this._powerHistory,i,a,e,s,n)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o){if(!n.sub_devices)return;const s=_(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=j(a);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${z(i)} ${E(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey,i=o.get(n)||[];let a=h.power;n.endsWith("_soc")?a=h.soc:n.endsWith("_soe")&&(a=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,s,a,!1,r?120:150)}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(!o||!this._topology)return;const s=this._topology.circuits[o];if(!s)return;const a=this._monitoringCache?.status?.circuits?.[s.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const r=this._graphSettingsCache.settings,c=r?.global_horizon||i,l=r?.circuits?.[o]?{...r.circuits[o],globalHorizon:c}:{horizon:c,has_override:!1,globalHorizon:c};n.open({...s,uuid:o,monitoringInfo:a,graphHorizonInfo:l})}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===M(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,p="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=j(o),h=a?n.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===c,f=o.type===l,_=g?G(o):null,v=g?O(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,_,v,b].filter(Boolean))),w=V(e,0,g,a,_,v);let x="";g?x="sub-device-bess":f&&(d++,d===r&&r%2==1&&(x="sub-device-full")),p+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${z(u)} ${E(u)}`:""}\n
\n ${w}\n ${y}\n
\n `}return p}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ne extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),f=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),f.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",te),customElements.define("span-panel-card-editor",ne),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function f(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const z=p.power;function E(e){return z.unit(e)}function k(e){return(e<0?"-":"")+z.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function M(e){return Math.ceil(e/2)}function P(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return M(t)===M(n)?"row-span":P(t)===P(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const _=n.entities?.power,f=_?l.states[_]:null,v=f&&parseFloat(f.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(f?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,z=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),M=x(d);let P;if("current"===M.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;P=`${M.format(i)}A`}else P=`${k(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=L?g:"#555",T=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${$}\n
\n
\n \n ${P}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,T)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`,devId:n})}return t}async function J(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):f(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of X(t)){let t;t=s&&s.has(o)?v(s.get(o)):f(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=f(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,_=g?n.states[g]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${k(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const z=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",z.icon),$.style.color=z.color,$.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,f,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=500,ee=Object.keys(u).filter(e=>"unknown"!==e);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=t("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,Z,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.subdevice_scales"),e.appendChild(n);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${n}`,Z,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(m(t.name),m(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of ee){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",te);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return f(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}for(const{entityId:t,key:n,devId:s}of X(this._topology)){const a=this._subDeviceHorizonMap?.get(s)||i;if(!o[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=v(a),d=b(l),p=e-l;y(this._powerHistory,n,c,e,p,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=f(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=j(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${k(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=h.power;n.endsWith("_soc")?c=h.soc:n.endsWith("_soe")&&(c=h.soe);const l=!!e.closest(".bess-chart-col");K(e,t,r,s?.has(i)?v(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const s=this._graphSettingsCache.settings,a=s?.global_horizon||i,r=s?.circuits?.[o]?{...s.circuits[o],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:o,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings,o=t?.global_horizon||i,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:o}:{horizon:o,has_override:!1,globalHorizon:o};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=M(Math.max(...n));0===P(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,p="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=j(o),h=a?n.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===c,_=o.type===l,f=g?G(o):null,v=g?O(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,f,v,b].filter(Boolean))),w=V(e,0,g,a,f,v);let x="";g?x="sub-device-bess":_&&(d++,d===r&&r%2==1&&(x="sub-device-full")),p+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${k(u)} ${E(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return p}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",ne),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index 068aac2..c28314b 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",l="pv",c="bess",d="evse",p="sub_",u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function _(e){return String(e).replace(/[&<>"']/g,e=>f[e])}const v=Object.keys(g).filter(e=>"unknown"!==e);class b extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,p=l?.circuits??{},u=document.createElement("div");u.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=n("sidepanel.graph_horizon"),u.appendChild(h);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(f),u.appendChild(g),s.appendChild(u),c?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(c.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,500,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:c.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(c),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{c.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${_(String(t.breaker_rating_a))}A · ${_(String(t.voltage))}V · Tabs [${_(String(t.tabs))}]`,i=this._createHeader(_(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,l=this._hass?.states?.[r]?.state;"on"===l&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,l=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===l&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,l=!0===r?.has_override,c=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))u.push({key:e,label:e});const h=l?c:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===h),o.classList.toggle("referenced","global"===h&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,l=null!=r&&!1!==r.monitoring_enabled;l&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,c.appendChild(p);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const h=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,f=r?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",h,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),s.addEventListener("change",()=>{const e=s.checked;c.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const _=p.querySelectorAll('input[type="radio"]');for(const e of _)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(a),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const h=document.createElement("span");return h.textContent=s,p.appendChild(u),p.appendChild(h),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,500,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),c.appendChild(d),c.appendChild(p),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function y(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",b);const x=u.power;function w(e){return x.unit(e)}function $(e){return(e<0?"-":"")+x.format(e)}function S(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function z(e){return e%2==0?1:0}function C(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":z(t)===z(n)?"col-span":"row-span"}function E(e){return u[e.chart_metric]||u[i]}function P(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class M{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,c,d,p,u){const h=t.entities?.power,f=h?c.states[h]:null,v=f&&parseFloat(f.state)||0,b=t.device_type===l||v<0,y=t.entities?.switch,x=y?c.states[y]:null,S=x?"on"===x.state:(f?.attributes?.relay_state||t.relay_state)===r,k=t.breaker_rating_a,z=k?`${Math.round(k)}A`:"",C=_(t.name||n("grid.unknown")),P=E(d);let M;if("current"===P.entityRole){const e=t.entities?.current,n=e?c.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${P.format(i)}A`}else M=`${$(v)}${w(v)}`;const A=g[u||"unknown"]||g.unknown;let N;N=A.icon2?`\n \n \n `:A.textLabel?`\n \n ${A.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",I=``;let q="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);q=`${Math.round(e)}%`}const D=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(S?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${q}\n ${I}\n
\n
\n
\n `}function N(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},T={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},q={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function D(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function H(e){return D(e,L)}function R(e){return D(e,T)}function j(e){return D(e,I)}function F(e){return D(e,q)}function G(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let l=s.original_name||r.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${_(l)}:\n ${_(d)}\n
\n `}return a}function O(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${_(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function W(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function B(e){const t=a[e];return t?t.ms:a[o].ms}function V(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function U(e){return Math.max(500,Math.floor(e/5e3))}function X(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function J(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function K(e,t,n,o,a,s,r,l){const{options:c,series:d}=function(e,t,n,o,a){n||(n=u[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,l=Date.now(),c=l-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e||[]).filter(e=>e.time>=c).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?Math.max(...h.map(e=>e[1])):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(f.min=n.fixedMin,f.max=n.fixedMax):m<1&&(f.min=0,f.max=1),a&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*a),g.push({type:"line",data:[[c,.8*a],[l,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[c,a],[l,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:c,max:l,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,l);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=c,p.data=d}function Q(e,t,i,o,a,s){if(!e||!i||!t)return;const c=W(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=S(o)),r&&(r.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=S(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=S(e),p&&(p.textContent="kW")}}const u=e.querySelector(".stat-solar .stat-value"),h=e.querySelector(".stat-solar .stat-unit");if(u){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);u.textContent=S(e)}else u.textContent="--";h&&(h.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=E(o),u="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const h=d.entities?.power,m=h?t.states[h]:null,f=m&&parseFloat(m.state)||0,_=d.device_type===l||f<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(u){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(f)}${w(f)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let k;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",_),d.always_on)k="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;k=n?n.state:"unknown"}const z=g[k]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",z.icon),C.style.color=z.color,C.title=z.label());const E=i.querySelector(".shedding-icon-secondary");E&&(z.icon2?(E.setAttribute("icon",z.icon2),E.style.color=z.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;K(M,t,e,s?.has(o)?B(s.get(o)):c,p,_,n,d.breaker_rating_a)}}}function Y(e,t,n,i,o){if(!n.sub_devices)return;const a=W(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const r=H(s);if(r){const e=t.states[r],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${w(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,i=o.get(n)||[];let s=h.power;n.endsWith("_soc")?s=h.soc:n.endsWith("_soe")&&(s=h.soe);const r=!!e.closest(".bess-chart-col");K(e,t,i,a,s,!1,r?120:150)}for(const e of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function Z(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=V(i),l=U(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,J(t,r,l))}}}function te(e,t,n){for(const{entityId:i,key:o}of function(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:H(i)};i.type===c&&(e.soc=R(i),e.soe=j(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`})}return t}(e))t.push(i),n.set(i,o)}async function ne(e,t,n,i,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?B(o.get(e)):W(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}const s=W(n);a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map}),te(t,a.get(s).entityIds,a.get(s).uuidByEntity);const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(Z(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ee(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class oe{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new M,this._graphSettingsCache=new ie,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await y(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const l=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=l?.circuits?.[e],n=t?.has_override?t.horizon:l?.global_horizon||o;this._horizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),u=(W(s),this._monitoringCache.status),h=function(e,t){const i=_(e.device_name||n("header.default_name")),o=_(e.serial||""),a=_(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,l=!!e.panel_entities?.dsm_state,c=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,u=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${u?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),f=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":C(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const l=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===z(e)?l.add(i):c.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),u=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!l.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=N(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!c.has(e))if(!u||"col-span"!==u.layout&&"single"!==u.layout)r.has(n)||(p+=N(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(u);p+=A(u.uuid,u.circuit,e,"3",u.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,u),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;if(!e.sub_devices)return"";const s=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===d&&!a));if(0===s.length)return"";const r=s.filter(([,e])=>e.type===d).length;let l=0,p="";for(const[e,o]of s){const a=o.type===d?n("subdevice.ev_charger"):o.type===c?n("subdevice.battery"):n("subdevice.fallback"),s=H(o),u=s?t.states[s]:null,h=u&&parseFloat(u.state)||0,g=o.type===c,m=o.type===d,f=g?R(o):null,v=g?j(o):null,b=g?F(o):null,y=G(o,t,i,new Set([s,f,v,b].filter(Boolean))),x=O(e,0,g,s,f,v);let S="";g?S="sub-device-bess":m&&(l++,l===r&&r%2==1&&(S="sub-device-full")),p+=`\n
\n
\n ${_(a)}\n ${_(o.name||"")}\n ${s?`${$(h)} ${w(h)}`:""}\n
\n ${x}\n ${y}\n
\n `}return p}(r,t,s);e.innerHTML=`\n \n ${h}\n ${m}\n ${v?`
${v}
`:""}\n ${!1!==s.show_panel?`\n
\n ${f}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}this._powerHistory.clear();try{await ne(this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)});try{await ne(t,r,s,this._powerHistory,this._horizonMap)}catch{}Q(e,t,r,s,this._powerHistory,this._horizonMap),Y(e,t,r,s,this._powerHistory);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Y(e,this._hass,r,this._config,this._powerHistory)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ne(this._hass,this._topology,this._config,n,t);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Q(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=B(i),d=V(c),p=U(c),u=e-c,h=this._powerHistory.get(t)||[];h.length>0&&e-h[h.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},c=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return;const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(!s||!t)return;const r=t.circuits[s];if(!r)return;await this._monitoringCache.fetch(this._hass);const l=this._monitoringCache?.status?.circuits?.[r.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const c=this._graphSettingsCache.settings,d=c?.global_horizon||o,p=c?.circuits?.[s]?{...c.circuits[s],globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d};a.open({...r,uuid:s,monitoringInfo:l,graphHorizonInfo:p})})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",se="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",re="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",ce="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function de(e,t,n,i,o){return`\n ${i}\n `}class pe{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,l=o?.circuits||{},c=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),u=a.notify_targets||"notify.notify",h=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",f=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(l).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(c),w=[...y,...x],$=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),S=w.some(([,e])=>!1!==e.monitoring_enabled),k=y.map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),z=Object.entries(c).map(([e,t])=>{const i=_(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=_(e);return`\n \n \n \n \n ${de(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${de(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${de(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${de(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=h.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${_(o)} (${_(e)})`:_(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${z}\n ${k}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!$&&S&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,l,c),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:l,service_data:this._serviceData({[c]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class ge{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let l=null;try{this._deviceId&&(l=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{l=null}const c=r?.global_horizon??o,d=r?.circuits??{},p=l?Object.entries(l.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],u=p.map(([e,t])=>{const i=_(t.name||e),o=d[e]||{},a=o.horizon??c,s=!0===o.has_override,r=_(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),h=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${u}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class me extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new oe,this._monitoringTab=new pe,this._settingsTab=new ge}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const l=this.shadowRoot.getElementById("panel-select");l&&l.addEventListener("change",()=>{this._selectedPanelId=l.value,localStorage.setItem("span_panel_selected",l.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",me),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=500,b=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),s.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,v,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,v,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),s.addEventListener("change",()=>{const e=s.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,v,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(a),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=s,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,v,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function C(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":C(t)===C(n)?"col-span":"row-span"}function M(e){return h[e.chart_metric]||h[i]}function P(e,t){const n=function(e){return M(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===c||v<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,k=z?`${Math.round(z)}A`:"",C=f(t.name||n("grid.unknown")),E=M(d);let P;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${E.format(i)}A`}else P=`${$(v)}${S(v)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${C}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return q(e,T)}function R(e){return q(e,D)}function G(e){return q(e,I)}function F(e){return q(e,H)}function O(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=a[e];return t?t.ms:a[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,a,s,r,c){const{options:l,series:d}=function(e,t,n,o,a){n||(n=h[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[l,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,a,s){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=M(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const k=g[z]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",k.icon),C.style.color=k.color,C.title=k.label());const E=i.querySelector(".shedding-icon-secondary");E&&(k.icon2?(E.setAttribute("icon",k.icon2),E.style.color=k.color,E.style.display=""):E.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".chart-container");if(P){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(P,t,e,s?.has(o)?V(s.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,a){if(!n.sub_devices)return;const s=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=j(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,a?.has(i)?V(a.get(i)):s,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=R(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,a){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let a;a=o&&o.has(e)?V(o.get(e)):B(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=a&&a.has(o)?V(a.get(o)):B(n),s.has(t)||s.set(t,{entityIds:[],uuidByEntity:new Map});const r=s.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class ae{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const c=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=c?.circuits?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._horizonMap.set(e,n)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const t=c?.sub_devices?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),h=(B(s),this._monitoringCache.status),u=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(h),_=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===C(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),h=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!c.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,h),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;if(!e.sub_devices)return"";const s=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!a));if(0===s.length)return"";const r=s.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of s){const a=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),s=j(o),h=s?t.states[s]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?R(o):null,v=g?G(o):null,b=g?F(o):null,y=O(o,t,i,new Set([s,_,v,b].filter(Boolean))),x=W(e,0,g,s,_,v);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(a)}\n ${f(o.name||"")}\n ${s?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(r,t,s);e.innerHTML=`\n \n ${u}\n ${m}\n ${v?`
${v}
`:""}\n ${!1!==s.show_panel?`\n
\n ${_}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,r,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)});try{await ie(t,r,s,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,r,s,this._powerHistory,this._horizonMap),Z(e,t,r,s,this._powerHistory,this._subDeviceHorizonMap);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(s&&t){const e=t.circuits[s];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[s]?{...n.circuits[s],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void a.open({...e,uuid:s,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,s=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void a.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:s})}})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const se="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=a.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),k=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${k}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!S&&$&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:c,service_data:this._serviceData({[l]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??l,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new ae,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const c=this.shadowRoot.getElementById("panel-select");c&&c.addEventListener("change",()=>{this._selectedPanelId=c.value,localStorage.setItem("span_panel_selected",c.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); From 98b02f342b9d44a367cc51eaf7873f3b8b771285 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:16:05 -0700 Subject: [PATCH 070/101] update image for card config --- images/config.png | Bin 347278 -> 343732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/config.png b/images/config.png index 1059656d4528e5f716aba302f49181df1a77e2b0..220aeaa223851f52e721d734a5a6eebc1cf4ae2e 100644 GIT binary patch literal 343732 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%0DVm*XsEdP$(IXGb4CGfWc$;U;A_JDy^6an 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 z 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�CL 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
ML) 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
`+OzR{nX_4k*abw$6{ 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

v_yn)t|D7zfh8X{SloER(Q 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%0AL@S^BXv1A@cA+5>Gh5E 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 V*EgJT+gspAURp(}RMI%`e*qMeR(${f From fb8a1a464053dad325667aabb5d7cfe4d6827d50 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:20:46 -0700 Subject: [PATCH 071/101] fix: multi-panel dropdown always visible with styled select Replace conditional text/select rendering with a styled dropdown that always shows, guard custom element registration against duplicates, and subscribe to device registry updates so panels refresh dynamically. --- src/index.js | 8 +++-- src/panel/index.js | 4 ++- src/panel/span-panel.js | 80 +++++++++++++++++++++++++++-------------- 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/src/index.js b/src/index.js index 2ed471d..b3f598b 100644 --- a/src/index.js +++ b/src/index.js @@ -2,8 +2,12 @@ import { CARD_VERSION } from "./constants.js"; import { SpanPanelCard } from "./card/span-panel-card.js"; import { SpanPanelCardEditor } from "./editor/span-panel-card-editor.js"; -customElements.define("span-panel-card", SpanPanelCard); -customElements.define("span-panel-card-editor", SpanPanelCardEditor); +if (!customElements.get("span-panel-card")) { + customElements.define("span-panel-card", SpanPanelCard); +} +if (!customElements.get("span-panel-card-editor")) { + customElements.define("span-panel-card-editor", SpanPanelCardEditor); +} window.customCards = window.customCards || []; window.customCards.push({ diff --git a/src/panel/index.js b/src/panel/index.js index ad258a3..99d6e8a 100644 --- a/src/panel/index.js +++ b/src/panel/index.js @@ -1,7 +1,9 @@ import { CARD_VERSION } from "../constants.js"; import { SpanPanelElement } from "./span-panel.js"; -customElements.define("span-panel", SpanPanelElement); +if (!customElements.get("span-panel")) { + customElements.define("span-panel", SpanPanelElement); +} console.warn( `%c SPAN-PANEL %c v${CARD_VERSION} `, diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 517e534..2ca92f9 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -29,15 +29,14 @@ const PANEL_STYLES = ` flex-grow: 1; } .panel-selector select { - background: transparent; - border: none; color: inherit; font-size: inherit; font-weight: inherit; cursor: pointer; - padding: 0; - appearance: none; - -webkit-appearance: none; + padding: 4px 8px; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 6px; + background-color: rgba(255, 255, 255, 0.1); } .panel-selector select option { background: var(--card-background-color, #333); @@ -105,6 +104,8 @@ export class SpanPanelElement extends HTMLElement { } }; document.addEventListener("visibilitychange", this._onVisibilityChange); + + this._subscribeDeviceRegistry(); } disconnectedCallback() { @@ -113,9 +114,43 @@ export class SpanPanelElement extends HTMLElement { document.removeEventListener("visibilitychange", this._onVisibilityChange); this._onVisibilityChange = null; } + this._unsubscribeDeviceRegistry(); + } + + _subscribeDeviceRegistry() { + if (this._deviceRegistryUnsub || !this._hass?.connection) return; + this._deviceRegistryUnsub = this._hass.connection.subscribeEvents(() => this._refreshPanels(), "device_registry_updated"); + } + + _unsubscribeDeviceRegistry() { + if (this._deviceRegistryUnsub) { + this._deviceRegistryUnsub.then(unsub => unsub()); + this._deviceRegistryUnsub = null; + } + } + + async _refreshPanels() { + if (!this._hass || !this._discovered) return; + + const devices = await this._hass.callWS({ + type: "config/device_registry/list", + }); + const panels = devices.filter(d => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN) && !d.via_device_id); + + const currentIds = new Set(this._panels.map(p => p.id)); + const newIds = new Set(panels.map(p => p.id)); + if (currentIds.size === newIds.size && [...currentIds].every(id => newIds.has(id))) return; + + this._panels = panels; + if (!this._panels.some(p => p.id === this._selectedPanelId) && this._panels.length > 0) { + this._selectedPanelId = this._panels[0].id; + localStorage.setItem("span_panel_selected", this._selectedPanelId); + } + this._render(); } set hass(val) { + const firstHass = !this._hass && val; this._hass = val; this._dashboardTab._hass = val; // Update ha-menu-button if already rendered @@ -124,6 +159,9 @@ export class SpanPanelElement extends HTMLElement { if (!this._discovered) { this._discoverPanels(); } + if (firstHass) { + this._subscribeDeviceRegistry(); + } } set narrow(val) { @@ -159,10 +197,6 @@ export class SpanPanelElement extends HTMLElement { _render() { setLanguage(this._hass?.language); - const multiPanel = this._panels.length > 1; - const selectedPanel = this._panels.find(p => p.id === this._selectedPanelId); - const panelLabel = selectedPanel ? selectedPanel.name_by_user || selectedPanel.name || selectedPanel.id : ""; - this.shadowRoot.innerHTML = ` @@ -171,23 +205,17 @@ export class SpanPanelElement extends HTMLElement {

- ${ - multiPanel - ? ` - - ` - : panelLabel - } +

From 4161d91e167cce615e855a12876a6548e2730fd0 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:20:57 -0700 Subject: [PATCH 072/101] fix: resize sub-device charts when browser window changes width Battery and EVSE chart rows did not auto-resize when the browser viewport narrowed because their containers lacked overflow constraints and no resize detection existed. Added overflow: hidden and min-width: 0 to .chart-container CSS, and a debounced ResizeObserver in both the card and panel dashboard that invalidates and recreates charts on width change. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.js | 2 ++ src/card/span-panel-card.js | 43 +++++++++++++++++++++++++++++++++++++ src/panel/tab-dashboard.js | 39 +++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 0481a1b..8fbe91b 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",p={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},h={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function f(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return p[e.chart_metric]||p[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const z=p.power;function E(e){return z.unit(e)}function k(e){return(e<0?"-":"")+z.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function M(e){return Math.ceil(e/2)}function P(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return M(t)===M(n)?"row-span":P(t)===P(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,p,h){const _=n.entities?.power,f=_?l.states[_]:null,v=f&&parseFloat(f.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(f?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,z=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),M=x(d);let P;if("current"===M.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;P=`${M.format(i)}A`}else P=`${k(v)}${E(v)}`;const N=u[h||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),D=L?g:"#555",T=``;let R="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${$}\n
\n
\n \n ${P}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,T)}function G(e){return F(e,R)}function O(e){return F(e,H)}function q(e){return F(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===c&&(e.soc=G(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`,devId:n})}return t}async function J(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):f(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of X(t)){let t;t=s&&s.has(o)?v(s.get(o)):f(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=p[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,h=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(i,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=f(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=$(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const p=x(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,_=g?n.states[g]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${k(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const z=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",z.icon),$.style.color=z.color,$.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,p,f,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=500,ee=Object.keys(u).filter(e=>"unknown"!==e);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=t("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,Z,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.subdevice_scales"),e.appendChild(n);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${n}`,Z,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(m(t.name),m(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of ee){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}s.appendChild(p),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),h.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),h.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",n)),h.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${n}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",te);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return f(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),p={},h=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}h&&c.startsWith(h+" ")&&(c=c.slice(h.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");p[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(p)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:p,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),p=e-l;y(this._powerHistory,t,c,e,p,d)}for(const{entityId:t,key:n,devId:s}of X(this._topology)){const a=this._subDeviceHorizonMap?.get(s)||i;if(!o[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=v(a),d=b(l),p=e-l;y(this._powerHistory,n,c,e,p,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=f(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=j(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${k(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=h.power;n.endsWith("_soc")?c=h.soc:n.endsWith("_soe")&&(c=h.soe);const l=!!e.closest(".bess-chart-col");K(e,t,r,s?.has(i)?v(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const s=this._graphSettingsCache.settings,a=s?.global_horizon||i,r=s?.circuits?.[o]?{...s.circuits[o],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:o,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings,o=t?.global_horizon||i,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:o}:{horizon:o,has_override:!1,globalHorizon:o};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=M(Math.max(...n));0===P(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=L(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,p="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=j(o),h=a?n.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===c,_=o.type===l,f=g?G(o):null,v=g?O(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,f,v,b].filter(Boolean))),w=V(e,0,g,a,f,v);let x="";g?x="sub-device-bess":_&&(d++,d===r&&r%2==1&&(x="sub-device-full")),p+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${k(u)} ${E(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return p}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=this.shadowRoot.querySelector(".slide-confirm");if(p){this._bindSlideConfirm(p,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const h=this.shadowRoot.querySelector("span-side-panel");h&&(h.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,p=parseInt(this._config.history_hours)||0,h=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(p,"0","23",t("editor.hours")),_=l(h,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.define("span-panel-card",ne),customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",h={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function f(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return h[e.chart_metric]||h[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const z=h.power;function E(e){return z.unit(e)}function k(e){return(e<0?"-":"")+z.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function M(e){return Math.ceil(e/2)}function P(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return M(t)===M(n)?"row-span":P(t)===P(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,h,p){const _=n.entities?.power,f=_?l.states[_]:null,v=f&&parseFloat(f.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(f?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,z=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),M=x(d);let P;if("current"===M.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;P=`${M.format(i)}A`}else P=`${k(v)}${E(v)}`;const N=u[p||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),D=L?g:"#555",T=``;let R="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${$}\n
\n
\n \n ${P}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function O(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function F(e){return O(e,T)}function j(e){return O(e,R)}function G(e){return O(e,H)}function q(e){return O(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:F(i)};i.type===c&&(e.soc=j(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`,devId:n})}return t}async function J(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):f(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of X(t)){let t;t=s&&s.has(o)?v(s.get(o)):f(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=h[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(i,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=f(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=$(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const h=x(o),p="current"===h.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,_=g?n.states[g]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(p){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${h.format(i)}A`}else x.innerHTML=`${k(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const z=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",z.icon),$.style.color=z.color,$.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,h,f,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=500,ee=Object.keys(u).filter(e=>"unknown"!==e);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,h=c?.circuits??{},p=document.createElement("div");p.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),p.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=t("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),p.appendChild(g),a.appendChild(p),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=h[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,Z,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.subdevice_scales"),e.appendChild(n);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${n}`,Z,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(m(t.name),m(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of ee){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,l.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=h.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(s),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,h.appendChild(p),h.appendChild(u),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(h),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",te);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._resizeObserver=null,this._lastCardWidth=0,this._resizeDebounce=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return f(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),h={},p=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");h[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(h)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:h,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),h=e-l;y(this._powerHistory,t,c,e,h,d)}for(const{entityId:t,key:n,devId:s}of X(this._topology)){const a=this._subDeviceHorizonMap?.get(s)||i;if(!o[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=v(a),d=b(l),h=e-l;y(this._powerHistory,n,c,e,h,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=f(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=F(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${k(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=p.power;n.endsWith("_soc")?c=p.soc:n.endsWith("_soe")&&(c=p.soe);const l=!!e.closest(".bess-chart-col");K(e,t,r,s?.has(i)?v(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const s=this._graphSettingsCache.settings,a=s?.global_horizon||i,r=s?.circuits?.[o]?{...s.circuits[o],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:o,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings,o=t?.global_horizon||i,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:o}:{horizon:o,has_override:!1,globalHorizon:o};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_invalidateCharts(){for(const e of this.shadowRoot.querySelectorAll(".chart-container")){const t=e.querySelector("ha-chart-base");t&&t.remove()}}_setupResizeObserver(){this._resizeObserver&&this._resizeObserver.disconnect();const e=this.shadowRoot.querySelector("ha-card");e&&(this._lastCardWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const t=e[0];if(!t)return;const n=t.contentRect.width;Math.abs(n-this._lastCardWidth)<5||(this._lastCardWidth=n,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{this._invalidateCharts(),this._updateDOM()},150))}),this._resizeObserver.observe(e))}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,h=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=M(Math.max(...n));0===P(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let h="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),p=a.get(n);if(h+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);h+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),h+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(h+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);h+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(h+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);h+=L(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}h+=`
${n}
`}return h}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,h="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=F(o),p=a?n.states[a]:null,u=p&&parseFloat(p.state)||0,g=o.type===c,_=o.type===l,f=g?j(o):null,v=g?G(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,f,v,b].filter(Boolean))),w=V(e,0,g,a,f,v);let x="";g?x="sub-device-bess":_&&(d++,d===r&&r%2==1&&(x="sub-device-full")),h+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${k(u)} ${E(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return h}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const h=this.shadowRoot.querySelector(".slide-confirm");if(h){this._bindSlideConfirm(h,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM(),this._setupResizeObserver()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,h=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(h,"0","23",t("editor.hours")),_=l(p,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(h)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.get("span-panel-card")||customElements.define("span-panel-card",ne),customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index c28314b..e0495c9 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const v=500,b=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",s.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),s.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,v,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=o.name||t,s.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(s);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,v,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const a=document.createElement("div");a.className="error-msg",a.id="error-msg",a.style.display="none",o.appendChild(a),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.breaker");const s=document.createElement("ha-switch");s.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&s.setAttribute("checked",""),s.addEventListener("change",()=>{const e=s.hasAttribute("checked")||s.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=n("sidepanel.priority_label");const s=document.createElement("select");s.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of b){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:s.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(a),o.appendChild(s),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(a))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.monitoring"),a.style.margin="0";const s=document.createElement("ha-switch");s.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&s.setAttribute("checked",""),o.appendChild(a),o.appendChild(s),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),s.addEventListener("change",()=>{const e=s.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const a=document.createElement("div");a.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,v,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),a=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),s=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:s,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),a.appendChild(s),a.appendChild(r),a}_createDurationRow(e,t,i,o,a,s,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(a),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=s,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,v,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function C(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":C(t)===C(n)?"col-span":"row-span"}function M(e){return h[e.chart_metric]||h[i]}function P(e,t){const n=function(e){return M(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,a,s,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,v=_&&parseFloat(_.state)||0,b=t.device_type===c||v<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,k=z?`${Math.round(z)}A`:"",C=f(t.name||n("grid.unknown")),E=M(d);let P;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${E.format(i)}A`}else P=`${$(v)}${S(v)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${C}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return q(e,T)}function R(e){return q(e,D)}function G(e){return q(e,I)}function F(e){return q(e,H)}function O(e,t,n,i){const o=n.visible_sub_entities||{};let a="";if(!e.entities)return a;for(const[n,s]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=s.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}a+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return a}function W(e,t,i,o,a,s){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!a},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!s},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=a[e];return t?t.ms:a[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,a){e.has(t)||e.set(t,[]);const s=e.get(t);for(s.push({time:i,value:n});s.length>0&&s[0].timea&&s.splice(0,s.length-a)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,a,s,r,c){const{options:l,series:d}=function(e,t,n,o,a){n||(n=h[i]);const s=o?"140, 160, 220":"77, 217, 175",r=`rgb(${s})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.35)`},{offset:1,color:`rgba(${s}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),a&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*a),g.push({type:"line",data:[[l,.8*a],[c,.8*a]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,a],[c,a]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,a,s,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,a,s){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const a="current"===(i.chart_metric||"power"),s=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(a){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;s&&(s.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}s&&(s.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(a){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=M(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const k=g[z]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",k.icon),C.style.color=k.color,C.title=k.label());const E=i.querySelector(".shedding-icon-secondary");E&&(k.icon2?(E.setAttribute("icon",k.icon2),E.style.color=k.color,E.style.display=""):E.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".chart-container");if(P){const e=a.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(P,t,e,s?.has(o)?V(s.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,a){if(!n.sub_devices)return;const s=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=j(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,a?.has(i)?V(a.get(i)):s,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:a,statistic_ids:t,period:s,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const a=new Date(Date.now()-i).toISOString(),s=await e.callWS({type:"history/history_during_period",start_time:a,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(s)){const i=n.get(e);if(!i||!t)continue;const a=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&a.push({time:n,value:t})}if(a.length>0){const e=o.get(i)||[],t=[...a,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=R(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,a){if(!t||!e)return;const s=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let a;a=o&&o.has(e)?V(o.get(e)):B(n),s.has(a)||s.set(a,{entityIds:[],uuidByEntity:new Map});const r=s.get(a);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=a&&a.has(o)?V(a.get(o)):B(n),s.has(t)||s.set(t,{entityIds:[],uuidByEntity:new Map});const r=s.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of s){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class ae{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null}async render(e,t,i,s){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=s;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const c=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=c?.circuits?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._horizonMap.set(e,n)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const t=c?.sub_devices?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),h=(B(s),this._monitoringCache.status),u=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),a=f(e.firmware||""),s="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${a}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,s),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],a=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,s=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${a>0?`${a} ${n(a>1?"status.warnings":"status.warning")}`:""}\n ${s>0?`${s} ${n(s>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(h),_=function(e,t,n,i,o,a){const s=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===C(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=a?(o=a,s=t,o?.circuits&&o.circuits[s]||null):null;var o,s;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,a=s.get(t),h=s.get(n);if(p+=`
${t}
`,a&&"row-span"===a.layout){const{monInfo:t,sheddingPriority:s}=d(a);p+=A(a.uuid,a.circuit,e,"2 / 4","row-span",0,i,o,t,s),p+=`
${n}
`;continue}if(!c.has(e))if(!a||"col-span"!==a.layout&&"single"!==a.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(a);p+=A(a.uuid,a.circuit,e,"2",a.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,s,h),v=function(e,t,i){const o=!1!==i.show_battery,a=!1!==i.show_evse;if(!e.sub_devices)return"";const s=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!a));if(0===s.length)return"";const r=s.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of s){const a=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),s=j(o),h=s?t.states[s]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?R(o):null,v=g?G(o):null,b=g?F(o):null,y=O(o,t,i,new Set([s,_,v,b].filter(Boolean))),x=W(e,0,g,s,_,v);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(a)}\n ${f(o.name||"")}\n ${s?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(r,t,s);e.innerHTML=`\n \n ${u}\n ${m}\n ${v?`
${v}
`:""}\n ${!1!==s.show_panel?`\n
\n ${_}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,r,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)});try{await ie(t,r,s,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,r,s,this._powerHistory,this._horizonMap),Z(e,t,r,s,this._powerHistory,this._subDeviceHorizonMap);const b=e.querySelector(".slide-confirm");b&&(this._bindSlideConfirm(b,e),e.classList.add("switches-disabled")),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)a[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!a[i]?.useRealtime)continue;const s=P(n,this._config);if(!s)continue;const r=this._hass.states[s];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,a=t-n.offsetLeft,s=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-a,s));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/s>=.9?(n.style.left=s+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const a=i.closest("[data-uuid]");if(!a||!t||!this._hass)return;const s=a.dataset.uuid,r=t.circuits[s];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const a=e.querySelector("span-side-panel");if(!a||!this._hass)return;if(a.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void a.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const s=i.dataset.uuid;if(s&&t){const e=t.circuits[s];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[s]?{...n.circuits[s],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void a.open({...e,uuid:s,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,s=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void a.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:s})}})}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null)}}const se="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const a=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=a.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=a.notification_title_template||"SPAN: {name} {alert_type}",m=a.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==a.enable_persistent_notifications,v=!1!==a.enable_event_bus,b=a.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${a?``:""}\n \n \n `}).join(""),k=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,a=!0===t.has_override,s=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${a?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,a=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${k}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!S&&$&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:s,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),a=e.querySelector("#global-status"),s=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,a.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const a=i.checked;o.style.opacity=a?"":"0.4",o.style.pointerEvents=a?"":"none";const s=e.querySelector("#global-status");try{if(a){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(s&&(s.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,s.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",s)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),a=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const s=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",s),this._notifyCloseHandler=s;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);a.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),a=e.querySelector("#g-title-template"),s=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),a&&a.addEventListener("input",()=>{r("notification_title_template",a.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const a=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:a})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:a})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:s,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:s,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,a=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:s,service:c,service_data:this._serviceData({[l]:o,[a]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,a="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(s,a,r),await this.render(e,t)})}}function ue(e){return Object.keys(a).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,a){let r;void 0!==i&&(this._configEntryId=i),void 0!==a&&(this._deviceId=a);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${s}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},a=o.horizon??l,s=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${s?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${s}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${s}`;e.innerHTML=`\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:s,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:s,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:s,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new ae,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}set hass(e){this._hass=e,this._dashboardTab._hass=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.hass=e),this._discovered||this._discoverPanels()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===s)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en";const o=this._panels.length>1,a=this._panels.find(e=>e.id===this._selectedPanelId),s=a?a.name_by_user||a.name||a.id:"";this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n ${o?`\n \n `:s}\n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const r=this.shadowRoot.querySelector("ha-menu-button");r&&(r.hass=this._hass,r.narrow=this._narrow);const c=this.shadowRoot.getElementById("panel-select");c&&c.addEventListener("change",()=>{this._selectedPanelId=c.value,localStorage.setItem("span_panel_selected",c.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=500,v=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,b,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,b,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function C(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":C(t)===C(n)?"col-span":"row-span"}function M(e){return h[e.chart_metric]||h[i]}function P(e,t){const n=function(e){return M(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,s,a,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===c||b<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,k=z?`${Math.round(z)}A`:"",C=f(t.name||n("grid.unknown")),E=M(d);let P;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${E.format(i)}A`}else P=`${$(b)}${S(b)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${C}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function R(e){return q(e,T)}function j(e){return q(e,D)}function O(e){return q(e,I)}function G(e){return q(e,H)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function W(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=s[e];return t?t.ms:s[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=h[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=M(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const k=g[z]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",k.icon),C.style.color=k.color,C.title=k.label());const E=i.querySelector(".shedding-icon-secondary");E&&(k.icon2?(E.setAttribute("icon",k.icon2),E.style.color=k.color,E.style.display=""):E.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".chart-container");if(P){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(P,t,e,a?.has(o)?V(a.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,s){if(!n.sub_devices)return;const a=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=R(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?V(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:R(i)};i.type===l&&(e.soc=j(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?V(o.get(e)):B(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=s&&s.has(o)?V(s.get(o)):B(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class se{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null,this._resizeObserver=null,this._lastContainerWidth=0,this._resizeDebounce=null,this._container=null}async render(e,t,i,a){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=a,this._container=e;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const c=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=c?.circuits?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._horizonMap.set(e,n)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const t=c?.sub_devices?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),h=(B(a),this._monitoringCache.status),u=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,a),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(h),_=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===C(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=A(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=A(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,a,h),b=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=R(o),h=a?t.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?j(o):null,b=g?O(o):null,v=g?G(o):null,y=F(o,t,i,new Set([a,_,b,v].filter(Boolean))),x=W(e,0,g,a,_,b);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(r,t,a);e.innerHTML=`\n \n ${u}\n ${m}\n ${b?`
${b}
`:""}\n ${!1!==a.show_panel?`\n
\n ${_}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,r,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)});try{await ie(t,r,a,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,r,a,this._powerHistory,this._horizonMap),Z(e,t,r,a,this._powerHistory,this._subDeviceHorizonMap);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._setupResizeObserver(e,r,a),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)s[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=P(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const s=i.closest("[data-uuid]");if(!s||!t||!this._hass)return;const a=s.dataset.uuid,r=t.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const s=e.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void s.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&t){const e=t.circuits[a];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[a]?{...n.circuits[a],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({...e,uuid:a,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,a=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:a})}})}_setupResizeObserver(e,t,n){this._resizeObserver&&this._resizeObserver.disconnect(),this._lastContainerWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(i=>{const o=i[0];if(!o)return;const s=o.contentRect.width;Math.abs(s-this._lastContainerWidth)<5||(this._lastContainerWidth=s,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}Y(e,this._hass,t,n,this._powerHistory,this._horizonMap),Z(e,this._hass,t,n,this._powerHistory,this._subDeviceHorizonMap)},150))}),this._resizeObserver.observe(e)}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const s=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=s.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=s.notification_title_template||"SPAN: {name} {alert_type}",m=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),k=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${s?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,s=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${k}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!S&&$&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),s=e.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){s.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none";const a=e.querySelector("#global-status");try{if(s){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(a&&(a.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,a.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",a)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),s=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const a=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);s.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),a=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),a&&a.addEventListener("input",()=>{r("notification_message_template",a.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const s=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[l]:o,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,s="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(a,s,r),await this.render(e,t)})}}function ue(e){return Object.keys(s).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,s){let r;void 0!==i&&(this._configEntryId=i),void 0!==s&&(this._deviceId=s);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${a}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},s=o.horizon??l,a=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${a?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${a}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${a}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:a,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:a,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:a,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new se,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry()}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this._hass?.connection&&(this._deviceRegistryUnsub=this._hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this._hass||!this._discovered)return;const e=(await this._hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=new Set(this._panels.map(e=>e.id)),n=new Set(e.map(e=>e.id));t.size===n.size&&[...t].every(e=>n.has(e))||(this._panels=e,!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0&&(this._selectedPanelId=this._panels[0].id,localStorage.setItem("span_panel_selected",this._selectedPanelId)),this._render())}set hass(e){const t=!this._hass&&e;this._hass=e,this._dashboardTab._hass=e;const n=this.shadowRoot.querySelector("ha-menu-button");n&&(n.hass=e),this._discovered||this._discoverPanels(),t&&this._subscribeDeviceRegistry()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en",this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n \n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const o=this.shadowRoot.querySelector("ha-menu-button");o&&(o.hass=this._hass,o.narrow=this._narrow);const s=this.shadowRoot.getElementById("panel-select");s&&s.addEventListener("change",()=>{this._selectedPanelId=s.value,localStorage.setItem("span_panel_selected",s.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.get("span-panel")||customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/card-styles.js b/src/card/card-styles.js index c303c4e..eea2c29 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -354,6 +354,8 @@ export const CARD_STYLES = ` width: 100%; aspect-ratio: 4 / 1; margin-top: 4px; + overflow: hidden; + min-width: 0; } .sub-devices { diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 53d75fc..1bb5876 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -42,6 +42,9 @@ export class SpanPanelCard extends HTMLElement { this._graphSettingsCache = new GraphSettingsCache(); this._horizonMap = new Map(); this._subDeviceHorizonMap = new Map(); + this._resizeObserver = null; + this._lastCardWidth = 0; + this._resizeDebounce = null; } connectedCallback() { @@ -106,6 +109,14 @@ export class SpanPanelCard extends HTMLElement { document.removeEventListener("visibilitychange", this._onVisibilityChange); this._onVisibilityChange = null; } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } + if (this._resizeDebounce) { + clearTimeout(this._resizeDebounce); + this._resizeDebounce = null; + } } setConfig(config) { @@ -499,6 +510,37 @@ export class SpanPanelCard extends HTMLElement { await this._loadHistory(); } + // ── Resize handling ──────────────────────────────────────────────────────── + + _invalidateCharts() { + for (const container of this.shadowRoot.querySelectorAll(".chart-container")) { + const chart = container.querySelector("ha-chart-base"); + if (chart) chart.remove(); + } + } + + _setupResizeObserver() { + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } + const card = this.shadowRoot.querySelector("ha-card"); + if (!card) return; + this._lastCardWidth = card.clientWidth; + this._resizeObserver = new ResizeObserver(entries => { + const entry = entries[0]; + if (!entry) return; + const newWidth = entry.contentRect.width; + if (Math.abs(newWidth - this._lastCardWidth) < 5) return; + this._lastCardWidth = newWidth; + if (this._resizeDebounce) clearTimeout(this._resizeDebounce); + this._resizeDebounce = setTimeout(() => { + this._invalidateCharts(); + this._updateDOM(); + }, 150); + }); + this._resizeObserver.observe(card); + } + // ── Full render ──────────────────────────────────────────────────────────── _render() { @@ -569,5 +611,6 @@ export class SpanPanelCard extends HTMLElement { this._rendered = true; this._recordPowerHistory(); this._updateDOM(); + this._setupResizeObserver(); } } diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 9134f92..7d45390 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -25,6 +25,10 @@ export class DashboardTab { this._subDeviceHorizonMap = new Map(); this._hass = null; this._config = null; + this._resizeObserver = null; + this._lastContainerWidth = 0; + this._resizeDebounce = null; + this._container = null; } async render(container, hass, deviceId, config) { @@ -32,6 +36,7 @@ export class DashboardTab { this._hass = hass; this._powerHistory.clear(); this._config = config; + this._container = container; try { const result = await discoverTopology(hass, deviceId); @@ -147,6 +152,8 @@ export class DashboardTab { container.classList.add("switches-disabled"); } + this._setupResizeObserver(container, topo, config); + // Start live update loop this._updateInterval = setInterval(() => { this._recordSamples(); @@ -389,6 +396,30 @@ export class DashboardTab { }); } + _setupResizeObserver(container, topo, config) { + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } + this._lastContainerWidth = container.clientWidth; + this._resizeObserver = new ResizeObserver(entries => { + const entry = entries[0]; + if (!entry) return; + const newWidth = entry.contentRect.width; + if (Math.abs(newWidth - this._lastContainerWidth) < 5) return; + this._lastContainerWidth = newWidth; + if (this._resizeDebounce) clearTimeout(this._resizeDebounce); + this._resizeDebounce = setTimeout(() => { + for (const cc of container.querySelectorAll(".chart-container")) { + const chart = cc.querySelector("ha-chart-base"); + if (chart) chart.remove(); + } + updateCircuitDOM(container, this._hass, topo, config, this._powerHistory, this._horizonMap); + updateSubDeviceDOM(container, this._hass, topo, config, this._powerHistory, this._subDeviceHorizonMap); + }, 150); + }); + this._resizeObserver.observe(container); + } + stop() { if (this._updateInterval) { clearInterval(this._updateInterval); @@ -398,5 +429,13 @@ export class DashboardTab { clearInterval(this._recorderRefreshInterval); this._recorderRefreshInterval = null; } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } + if (this._resizeDebounce) { + clearTimeout(this._resizeDebounce); + this._resizeDebounce = null; + } } } From d62f62c415116efa2ad5b21d92ec73e689fd69b8 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:41:54 -0700 Subject: [PATCH 073/101] fix: move gear and slider inline with panel identity row Place the settings gear and enable-switches slider next to the serial number in panel-identity with flex-wrap for narrow viewports. Remove the separate header-center container. Also override aspect-ratio on sub-device chart containers so x-axis labels are not clipped. --- src/card/card-styles.js | 12 ++++++++---- src/core/header-renderer.js | 14 ++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/card/card-styles.js b/src/card/card-styles.js index eea2c29..99ddba1 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.js @@ -28,8 +28,9 @@ export const CARD_STYLES = ` .panel-identity { display: flex; - align-items: baseline; - gap: 12px; + align-items: center; + flex-wrap: wrap; + gap: 8px 12px; margin-bottom: 12px; } @@ -86,6 +87,9 @@ export const CARD_STYLES = ` justify-content: center; padding-top: 8px; } + .panel-identity .panel-gear { + margin-left: 0; + } .slide-confirm { position: relative; display: inline-flex; @@ -383,7 +387,7 @@ export const CARD_STYLES = ` .sub-device-name { font-size: 0.85em; color: var(--secondary-text-color, #999); flex: 1; } .sub-power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; } .sub-power-value strong { font-weight: 700; font-size: 1.1em; } - .sub-device .chart-container { margin-bottom: 8px; } + .sub-device .chart-container { margin-bottom: 8px; aspect-ratio: auto; } .bess-charts { display: grid; @@ -400,7 +404,7 @@ export const CARD_STYLES = ` color: var(--secondary-text-color, #999); margin-bottom: 4px; } - .bess-chart-col .chart-container { } + .bess-chart-col .chart-container { aspect-ratio: auto; } .sub-entity { display: flex; gap: 6px; padding: 3px 0; font-size: 0.85em; } .sub-entity-name { color: var(--secondary-text-color, #999); } .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); } diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index 79d823d..80172c8 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -30,6 +30,12 @@ export function buildHeaderHTML(topology, config) { +
+ ${t("header.enable_switches")} +
+ +
+
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n
\n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
${ @@ -105,14 +111,6 @@ export function buildHeaderHTML(topology, config) { }
-
-
- ${t("header.enable_switches")} -
- -
-
-
${firmware} From 7989f25df3754f2b2ab1005d0c19cce5e46cb258 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:42:00 -0700 Subject: [PATCH 074/101] chore: update dist with header layout and chart aspect-ratio fixes --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 8fbe91b..6dc88d7 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",h={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function f(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return h[e.chart_metric]||h[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const z=h.power;function E(e){return z.unit(e)}function k(e){return(e<0?"-":"")+z.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function M(e){return Math.ceil(e/2)}function P(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return M(t)===M(n)?"row-span":P(t)===P(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,h,p){const _=n.entities?.power,f=_?l.states[_]:null,v=f&&parseFloat(f.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(f?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,z=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),M=x(d);let P;if("current"===M.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;P=`${M.format(i)}A`}else P=`${k(v)}${E(v)}`;const N=u[p||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),D=L?g:"#555",T=``;let R="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${$}\n
\n
\n \n ${P}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function O(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function F(e){return O(e,T)}function j(e){return O(e,R)}function G(e){return O(e,H)}function q(e){return O(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:F(i)};i.type===c&&(e.soc=j(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`,devId:n})}return t}async function J(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):f(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of X(t)){let t;t=s&&s.has(o)?v(s.get(o)):f(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=h[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(i,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=f(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=$(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const h=x(o),p="current"===h.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,_=g?n.states[g]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(p){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${h.format(i)}A`}else x.innerHTML=`${k(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const z=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",z.icon),$.style.color=z.color,$.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,h,f,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=500,ee=Object.keys(u).filter(e=>"unknown"!==e);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,h=c?.circuits??{},p=document.createElement("div");p.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),p.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=t("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),p.appendChild(g),a.appendChild(p),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=h[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,Z,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.subdevice_scales"),e.appendChild(n);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${n}`,Z,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(m(t.name),m(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of ee){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,l.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=h.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(s),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,h.appendChild(p),h.appendChild(u),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(h),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",te);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._resizeObserver=null,this._lastCardWidth=0,this._resizeDebounce=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return f(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),h={},p=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");h[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(h)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:h,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),h=e-l;y(this._powerHistory,t,c,e,h,d)}for(const{entityId:t,key:n,devId:s}of X(this._topology)){const a=this._subDeviceHorizonMap?.get(s)||i;if(!o[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=v(a),d=b(l),h=e-l;y(this._powerHistory,n,c,e,h,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=f(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=F(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${k(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=p.power;n.endsWith("_soc")?c=p.soc:n.endsWith("_soe")&&(c=p.soe);const l=!!e.closest(".bess-chart-col");K(e,t,r,s?.has(i)?v(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const s=this._graphSettingsCache.settings,a=s?.global_horizon||i,r=s?.circuits?.[o]?{...s.circuits[o],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:o,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings,o=t?.global_horizon||i,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:o}:{horizon:o,has_override:!1,globalHorizon:o};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_invalidateCharts(){for(const e of this.shadowRoot.querySelectorAll(".chart-container")){const t=e.querySelector("ha-chart-base");t&&t.remove()}}_setupResizeObserver(){this._resizeObserver&&this._resizeObserver.disconnect();const e=this.shadowRoot.querySelector("ha-card");e&&(this._lastCardWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const t=e[0];if(!t)return;const n=t.contentRect.width;Math.abs(n-this._lastCardWidth)<5||(this._lastCardWidth=n,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{this._invalidateCharts(),this._updateDOM()},150))}),this._resizeObserver.observe(e))}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,h=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=M(Math.max(...n));0===P(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let h="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),p=a.get(n);if(h+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);h+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),h+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(h+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);h+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(h+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);h+=L(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}h+=`
${n}
`}return h}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,h="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=F(o),p=a?n.states[a]:null,u=p&&parseFloat(p.state)||0,g=o.type===c,_=o.type===l,f=g?j(o):null,v=g?G(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,f,v,b].filter(Boolean))),w=V(e,0,g,a,f,v);let x="";g?x="sub-device-bess":_&&(d++,d===r&&r%2==1&&(x="sub-device-full")),h+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${k(u)} ${E(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return h}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const h=this.shadowRoot.querySelector(".slide-confirm");if(h){this._bindSlideConfirm(h,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM(),this._setupResizeObserver()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,h=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(h,"0","23",t("editor.hours")),_=l(p,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(h)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.get("span-panel-card")||customElements.define("span-panel-card",ne),customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",h={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function f(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return h[e.chart_metric]||h[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const z=h.power;function E(e){return z.unit(e)}function k(e){return(e<0?"-":"")+z.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function M(e){return Math.ceil(e/2)}function P(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return M(t)===M(n)?"row-span":P(t)===P(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,h,p){const _=n.entities?.power,f=_?l.states[_]:null,v=f&&parseFloat(f.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(f?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,z=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),M=x(d);let P;if("current"===M.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;P=`${M.format(i)}A`}else P=`${k(v)}${E(v)}`;const N=u[p||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),D=L?g:"#555",T=``;let R="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${$}\n
\n
\n \n ${P}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function O(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function F(e){return O(e,T)}function j(e){return O(e,R)}function G(e){return O(e,H)}function q(e){return O(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:F(i)};i.type===c&&(e.soc=j(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`,devId:n})}return t}async function J(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):f(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of X(t)){let t;t=s&&s.has(o)?v(s.get(o)):f(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=h[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(i,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=f(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=$(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const h=x(o),p="current"===h.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,_=g?n.states[g]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(p){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${h.format(i)}A`}else x.innerHTML=`${k(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const z=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",z.icon),$.style.color=z.color,$.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,h,f,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=500,ee=Object.keys(u).filter(e=>"unknown"!==e);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,h=c?.circuits??{},p=document.createElement("div");p.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),p.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=t("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),p.appendChild(g),a.appendChild(p),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=h[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,Z,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.subdevice_scales"),e.appendChild(n);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${n}`,Z,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(m(t.name),m(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of ee){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,l.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=h.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(s),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,h.appendChild(p),h.appendChild(u),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(h),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",te);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._resizeObserver=null,this._lastCardWidth=0,this._resizeDebounce=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return f(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),h={},p=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");h[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(h)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:h,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),h=e-l;y(this._powerHistory,t,c,e,h,d)}for(const{entityId:t,key:n,devId:s}of X(this._topology)){const a=this._subDeviceHorizonMap?.get(s)||i;if(!o[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=v(a),d=b(l),h=e-l;y(this._powerHistory,n,c,e,h,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=f(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=F(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${k(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=p.power;n.endsWith("_soc")?c=p.soc:n.endsWith("_soe")&&(c=p.soe);const l=!!e.closest(".bess-chart-col");K(e,t,r,s?.has(i)?v(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const s=this._graphSettingsCache.settings,a=s?.global_horizon||i,r=s?.circuits?.[o]?{...s.circuits[o],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:o,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings,o=t?.global_horizon||i,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:o}:{horizon:o,has_override:!1,globalHorizon:o};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_invalidateCharts(){for(const e of this.shadowRoot.querySelectorAll(".chart-container")){const t=e.querySelector("ha-chart-base");t&&t.remove()}}_setupResizeObserver(){this._resizeObserver&&this._resizeObserver.disconnect();const e=this.shadowRoot.querySelector("ha-card");e&&(this._lastCardWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const t=e[0];if(!t)return;const n=t.contentRect.width;Math.abs(n-this._lastCardWidth)<5||(this._lastCardWidth=n,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{this._invalidateCharts(),this._updateDOM()},150))}),this._resizeObserver.observe(e))}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,h=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=M(Math.max(...n));0===P(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let h="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),p=a.get(n);if(h+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);h+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),h+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(h+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);h+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(h+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);h+=L(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}h+=`
${n}
`}return h}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,h="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=F(o),p=a?n.states[a]:null,u=p&&parseFloat(p.state)||0,g=o.type===c,_=o.type===l,f=g?j(o):null,v=g?G(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,f,v,b].filter(Boolean))),w=V(e,0,g,a,f,v);let x="";g?x="sub-device-bess":_&&(d++,d===r&&r%2==1&&(x="sub-device-full")),h+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${k(u)} ${E(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return h}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const h=this.shadowRoot.querySelector(".slide-confirm");if(h){this._bindSlideConfirm(h,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM(),this._setupResizeObserver()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,h=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(h,"0","23",t("editor.hours")),_=l(p,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(h)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.get("span-panel-card")||customElements.define("span-panel-card",ne),customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index e0495c9..a67b0f6 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=500,v=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,b,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,b,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function C(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":C(t)===C(n)?"col-span":"row-span"}function M(e){return h[e.chart_metric]||h[i]}function P(e,t){const n=function(e){return M(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,s,a,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===c||b<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,k=z?`${Math.round(z)}A`:"",C=f(t.name||n("grid.unknown")),E=M(d);let P;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${E.format(i)}A`}else P=`${$(b)}${S(b)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${C}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function R(e){return q(e,T)}function j(e){return q(e,D)}function O(e){return q(e,I)}function G(e){return q(e,H)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function W(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=s[e];return t?t.ms:s[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=h[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=M(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const k=g[z]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",k.icon),C.style.color=k.color,C.title=k.label());const E=i.querySelector(".shedding-icon-secondary");E&&(k.icon2?(E.setAttribute("icon",k.icon2),E.style.color=k.color,E.style.display=""):E.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".chart-container");if(P){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(P,t,e,a?.has(o)?V(a.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,s){if(!n.sub_devices)return;const a=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=R(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?V(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:R(i)};i.type===l&&(e.soc=j(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?V(o.get(e)):B(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=s&&s.has(o)?V(s.get(o)):B(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class se{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null,this._resizeObserver=null,this._lastContainerWidth=0,this._resizeDebounce=null,this._container=null}async render(e,t,i,a){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=a,this._container=e;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const c=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=c?.circuits?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._horizonMap.set(e,n)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const t=c?.sub_devices?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),h=(B(a),this._monitoringCache.status),u=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,a),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(h),_=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===C(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=A(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=A(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,a,h),b=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=R(o),h=a?t.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?j(o):null,b=g?O(o):null,v=g?G(o):null,y=F(o,t,i,new Set([a,_,b,v].filter(Boolean))),x=W(e,0,g,a,_,b);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(r,t,a);e.innerHTML=`\n \n ${u}\n ${m}\n ${b?`
${b}
`:""}\n ${!1!==a.show_panel?`\n
\n ${_}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,r,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)});try{await ie(t,r,a,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,r,a,this._powerHistory,this._horizonMap),Z(e,t,r,a,this._powerHistory,this._subDeviceHorizonMap);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._setupResizeObserver(e,r,a),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)s[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=P(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const s=i.closest("[data-uuid]");if(!s||!t||!this._hass)return;const a=s.dataset.uuid,r=t.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const s=e.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void s.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&t){const e=t.circuits[a];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[a]?{...n.circuits[a],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({...e,uuid:a,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,a=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:a})}})}_setupResizeObserver(e,t,n){this._resizeObserver&&this._resizeObserver.disconnect(),this._lastContainerWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(i=>{const o=i[0];if(!o)return;const s=o.contentRect.width;Math.abs(s-this._lastContainerWidth)<5||(this._lastContainerWidth=s,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}Y(e,this._hass,t,n,this._powerHistory,this._horizonMap),Z(e,this._hass,t,n,this._powerHistory,this._subDeviceHorizonMap)},150))}),this._resizeObserver.observe(e)}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const s=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=s.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=s.notification_title_template||"SPAN: {name} {alert_type}",m=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),k=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${s?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,s=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${k}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!S&&$&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),s=e.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){s.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none";const a=e.querySelector("#global-status");try{if(s){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(a&&(a.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,a.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",a)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),s=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const a=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);s.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),a=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),a&&a.addEventListener("input",()=>{r("notification_message_template",a.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const s=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[l]:o,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,s="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(a,s,r),await this.render(e,t)})}}function ue(e){return Object.keys(s).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,s){let r;void 0!==i&&(this._configEntryId=i),void 0!==s&&(this._deviceId=s);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${a}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},s=o.horizon??l,a=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${a?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${a}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${a}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:a,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:a,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:a,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new se,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry()}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this._hass?.connection&&(this._deviceRegistryUnsub=this._hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this._hass||!this._discovered)return;const e=(await this._hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=new Set(this._panels.map(e=>e.id)),n=new Set(e.map(e=>e.id));t.size===n.size&&[...t].every(e=>n.has(e))||(this._panels=e,!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0&&(this._selectedPanelId=this._panels[0].id,localStorage.setItem("span_panel_selected",this._selectedPanelId)),this._render())}set hass(e){const t=!this._hass&&e;this._hass=e,this._dashboardTab._hass=e;const n=this.shadowRoot.querySelector("ha-menu-button");n&&(n.hass=e),this._discovered||this._discoverPanels(),t&&this._subscribeDeviceRegistry()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en",this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n \n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const o=this.shadowRoot.querySelector("ha-menu-button");o&&(o.hass=this._hass,o.narrow=this._narrow);const s=this.shadowRoot.getElementById("panel-select");s&&s.addEventListener("change",()=>{this._selectedPanelId=s.value,localStorage.setItem("span_panel_selected",s.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.get("span-panel")||customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=500,v=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,b,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,b,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function C(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":C(t)===C(n)?"col-span":"row-span"}function M(e){return h[e.chart_metric]||h[i]}function P(e,t){const n=function(e){return M(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,s,a,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===c||b<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,k=z?`${Math.round(z)}A`:"",C=f(t.name||n("grid.unknown")),E=M(d);let P;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${E.format(i)}A`}else P=`${$(b)}${S(b)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${C}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function R(e){return q(e,T)}function j(e){return q(e,D)}function O(e){return q(e,I)}function G(e){return q(e,H)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function W(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=s[e];return t?t.ms:s[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=h[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=M(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const k=g[z]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",k.icon),C.style.color=k.color,C.title=k.label());const E=i.querySelector(".shedding-icon-secondary");E&&(k.icon2?(E.setAttribute("icon",k.icon2),E.style.color=k.color,E.style.display=""):E.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".chart-container");if(P){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(P,t,e,a?.has(o)?V(a.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,s){if(!n.sub_devices)return;const a=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=R(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?V(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:R(i)};i.type===l&&(e.soc=j(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?V(o.get(e)):B(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=s&&s.has(o)?V(s.get(o)):B(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class se{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null,this._resizeObserver=null,this._lastContainerWidth=0,this._resizeDebounce=null,this._container=null}async render(e,t,i,a){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=a,this._container=e;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const c=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=c?.circuits?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._horizonMap.set(e,n)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const t=c?.sub_devices?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),h=(B(a),this._monitoringCache.status),u=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,a),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(h),_=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===C(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=A(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=A(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,a,h),b=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=R(o),h=a?t.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?j(o):null,b=g?O(o):null,v=g?G(o):null,y=F(o,t,i,new Set([a,_,b,v].filter(Boolean))),x=W(e,0,g,a,_,b);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(r,t,a);e.innerHTML=`\n \n ${u}\n ${m}\n ${b?`
${b}
`:""}\n ${!1!==a.show_panel?`\n
\n ${_}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,r,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)});try{await ie(t,r,a,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,r,a,this._powerHistory,this._horizonMap),Z(e,t,r,a,this._powerHistory,this._subDeviceHorizonMap);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._setupResizeObserver(e,r,a),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)s[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=P(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const s=i.closest("[data-uuid]");if(!s||!t||!this._hass)return;const a=s.dataset.uuid,r=t.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const s=e.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void s.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&t){const e=t.circuits[a];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[a]?{...n.circuits[a],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({...e,uuid:a,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,a=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:a})}})}_setupResizeObserver(e,t,n){this._resizeObserver&&this._resizeObserver.disconnect(),this._lastContainerWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(i=>{const o=i[0];if(!o)return;const s=o.contentRect.width;Math.abs(s-this._lastContainerWidth)<5||(this._lastContainerWidth=s,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}Y(e,this._hass,t,n,this._powerHistory,this._horizonMap),Z(e,this._hass,t,n,this._powerHistory,this._subDeviceHorizonMap)},150))}),this._resizeObserver.observe(e)}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const s=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=s.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=s.notification_title_template||"SPAN: {name} {alert_type}",m=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),k=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${s?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,s=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${k}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!S&&$&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),s=e.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){s.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none";const a=e.querySelector("#global-status");try{if(s){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(a&&(a.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,a.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",a)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),s=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const a=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);s.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),a=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),a&&a.addEventListener("input",()=>{r("notification_message_template",a.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const s=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[l]:o,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,s="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(a,s,r),await this.render(e,t)})}}function ue(e){return Object.keys(s).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,s){let r;void 0!==i&&(this._configEntryId=i),void 0!==s&&(this._deviceId=s);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${a}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},s=o.horizon??l,a=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${a?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${a}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${a}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:a,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:a,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:a,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new se,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry()}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this._hass?.connection&&(this._deviceRegistryUnsub=this._hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this._hass||!this._discovered)return;const e=(await this._hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=new Set(this._panels.map(e=>e.id)),n=new Set(e.map(e=>e.id));t.size===n.size&&[...t].every(e=>n.has(e))||(this._panels=e,!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0&&(this._selectedPanelId=this._panels[0].id,localStorage.setItem("span_panel_selected",this._selectedPanelId)),this._render())}set hass(e){const t=!this._hass&&e;this._hass=e,this._dashboardTab._hass=e;const n=this.shadowRoot.querySelector("ha-menu-button");n&&(n.hass=e),this._discovered||this._discoverPanels(),t&&this._subscribeDeviceRegistry()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en",this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n \n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const o=this.shadowRoot.querySelector("ha-menu-button");o&&(o.hass=this._hass,o.narrow=this._narrow);const s=this.shadowRoot.getElementById("panel-select");s&&s.addEventListener("change",()=>{this._selectedPanelId=s.value,localStorage.setItem("span_panel_selected",s.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.get("span-panel")||customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); From 6606a2d4463444041634592fbdb1e37eb18db92e Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:51:28 -0700 Subject: [PATCH 075/101] update for unecessary usage of HACs --- README.md | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a78ae30..66bf928 100644 --- a/README.md +++ b/README.md @@ -18,30 +18,23 @@ positions. ## Requirements -- [SPAN Panel integration](https://github.com/SpanPanel/span) installed and configured +- [SPAN Panel integration](https://github.com/SpanPanel/span) **v2.0.5 or later** installed and configured - Circuits must have `tabs` attributes (included in SPAN Panel integration v1.2+) ## Installation -### HACS (Custom Repository) - -1. Open HACS in Home Assistant -2. Click the three-dot menu in the top right and select **Custom repositories** -3. Add the repository URL and select **Dashboard** as the category -4. Click **Add** -5. Search for "SPAN Panel Card" in HACS and click **Install** -6. Restart Home Assistant - -### Manual - -1. Download `span-panel-card.js` from the [latest release](../../releases/latest) -2. Copy the file to your `config/www/` directory -3. Add the resource in Home Assistant: - - Go to **Settings > Dashboards > Resources** - - Click **Add Resource** - - URL: `/local/span-panel-card.js` - - Type: **JavaScript Module** -4. Refresh your browser +As of SPAN Panel integration **v2.0.5**, this card is automatically registered as a Lovelace resource by the integration itself — no separate installation is +required. The integration serves the card JS from its own static path and writes the resource entry on setup. + +> **Migrating from HACS or manual install?** If you previously installed this card via HACS or manually copied `span-panel-card.js` to your `www/` directory, +> you should: +> +> 1. Remove the HACS-managed dashboard resource (HACS > Dashboard > SPAN Panel Card > Remove) — or delete the manual `/local/span-panel-card.js` resource from +> **Settings > Dashboards > Resources** +> 2. Remove `span-panel-card.js` from your `config/www/` directory if present +> 3. Restart Home Assistant +> +> The integration will register its own copy of the card automatically on next startup. ## Configuration From b442dc7b3dd102baa63cb9053547d169f3ea4ebd Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:53:54 -0700 Subject: [PATCH 076/101] chore: remove HACS manifest and CI validation Card is auto-registered by the span integration as of v2.0.5, making HACS distribution unnecessary. --- .github/workflows/ci.yml | 9 --------- hacs.json | 5 ----- 2 files changed, 14 deletions(-) delete mode 100644 hacs.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da21431..286b2ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,12 +34,3 @@ jobs: - run: npm ci - run: npm run build - - hacs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: hacs/action@main - with: - category: plugin diff --git a/hacs.json b/hacs.json deleted file mode 100644 index d9caec0..0000000 --- a/hacs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "SPAN Panel Card", - "render_readme": true, - "filename": "span-panel-card.js" -} From a24aceee8513eefbf64845977dd8cad23111ec81 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Wed, 1 Apr 2026 23:06:26 -0700 Subject: [PATCH 077/101] fix: address all 10 copilot review comments on PR #3 - Align CARD_VERSION with package.json (0.9.0) - Call setLanguage in card hass setter for i18n support - Pass configEntryId to GraphSettingsCache and MonitoringStatusCache for multi-panel correctness - Store container event listeners and remove in stop() to prevent stacking on re-render - Add stop() to MonitoringTab and SettingsTab for cleanup - Call stop() on all tabs in disconnectedCallback - Fix panel gear tooltip to say "graph settings" not "monitoring" - Use i18n horizon labels in tab-settings and side-panel selects --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/span-panel-card.js | 19 ++++++++++++------- src/constants.js | 2 +- src/core/header-renderer.js | 2 +- src/core/monitoring-status.js | 6 ++++-- src/core/side-panel.js | 6 +++--- src/i18n.js | 5 +++++ src/panel/span-panel.js | 6 +++++- src/panel/tab-dashboard.js | 33 ++++++++++++++++++++++----------- src/panel/tab-monitoring.js | 11 +++++++++++ src/panel/tab-settings.js | 9 ++++++++- 12 files changed, 74 insertions(+), 29 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 6dc88d7..588c4f7 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";const e={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function t(t){return e.en?.[t]??e.en[t]??t}const n="power",i="5m",o={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",a="CLOSED",r="pv",c="bess",l="evse",d="sub_",h={power:{entityRole:"power",label:()=>t("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>t("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},p={soc:{label:()=>t("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>t("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},u={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>t("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>t("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>t("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>t("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>t("shedding.unknown")}},g="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function m(e){return String(e).replace(/[&<>"']/g,e=>_[e])}function f(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function v(e){const t=o[e];return t?t.ms:o[i].ms}function b(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function y(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function w(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function x(e){return h[e.chart_metric]||h[n]}function C(e,t){const n=function(e){return x(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class S{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:s,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const z=h.power;function E(e){return z.unit(e)}function k(e){return(e<0?"-":"")+z.format(e)}function $(e){return(Math.abs(e)/1e3).toFixed(1)}function M(e){return Math.ceil(e/2)}function P(e){return e%2==0?1:0}function N(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return M(t)===M(n)?"row-span":P(t)===P(n)?"col-span":"row-span"}class A{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:s,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function L(e,n,i,o,s,c,l,d,h,p){const _=n.entities?.power,f=_?l.states[_]:null,v=f&&parseFloat(f.state)||0,b=n.device_type===r||v<0,y=n.entities?.switch,w=y?l.states[y]:null,C=w?"on"===w.state:(f?.attributes?.relay_state||n.relay_state)===a,S=n.breaker_rating_a,z=S?`${Math.round(S)}A`:"",$=m(n.name||t("grid.unknown")),M=x(d);let P;if("current"===M.entityRole){const e=n.entities?.current,t=e?l.states[e]:null,i=t&&parseFloat(t.state)||0;P=`${M.format(i)}A`}else P=`${k(v)}${E(v)}`;const N=u[p||"unknown"]||u.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),D=L?g:"#555",T=``;let R="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${$}\n
\n
\n \n ${P}\n \n ${!1!==n.is_user_controllable&&n.entities?.switch?`\n
\n ${t(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function D(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},R={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},H={names:["state of energy"],suffixes:["_soe_kwh"]},I={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function O(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function F(e){return O(e,T)}function j(e){return O(e,R)}function G(e){return O(e,H)}function q(e){return O(e,I)}function W(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${m(c)}:\n ${m(d)}\n
\n `}return s}function V(e,n,i,o,s,a){if(i){return`\n
\n ${[{key:`${d}${e}_soc`,title:t("subdevice.soc"),available:!!s},{key:`${d}${e}_soe`,title:t("subdevice.soe"),available:!!a},{key:`${d}${e}_power`,title:t("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${m(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function B(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=b(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,w(t,r,c))}}}function X(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:F(i)};i.type===c&&(e.soc=j(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${d}${n}_${i}`,devId:n})}return t}async function J(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=C(i,n);if(!t)continue;let s;s=o&&o.has(e)?v(o.get(e)):f(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of X(t)){let t;t=s&&s.has(o)?v(s.get(o)):f(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(B(e,n.entityIds,n.uuidByEntity,t,i)):r.push(U(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function K(e,t,i,o,s,a,r,c){const{options:l,series:d}=function(e,t,i,o,s){i||(i=h[n]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==i.fixedMin&&void 0!==i.fixedMax,p=i.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>i.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=i.fixedMin,m.max=i.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===i.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(i,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Q(e,n,i,o,s,c){if(!e||!i||!n)return;const l=f(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const t=e.entities?.power;if(!t)continue;const i=n.states[t],o=i&&parseFloat(i.state)||0;e.device_type!==r&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=$(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=$(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=$(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=$(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,n,i,o,d);const h=x(o),p="current"===h.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const g=d.entities?.power,_=g?n.states[g]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===r||m<0,b=d.entities?.switch,y=b?n.states[b]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===a,x=i.querySelector(".power-value");if(x)if(p){const e=d.entities?.current,t=e?n.states[e]:null,i=t&&parseFloat(t.state)||0;x.innerHTML=`${h.format(i)}A`}else x.innerHTML=`${k(m)}${E(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=t(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,t=e?n.states[e]:null;S=t?t.state:"unknown"}const z=u[S]||u.unknown,$=i.querySelector(".shedding-icon");$&&($.setAttribute("icon",z.icon),$.style.color=z.color,$.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],t=i.classList.contains("circuit-col-span")?200:100;K(N,n,e,c?.has(o)?v(c.get(o)):l,h,f,t,d.breaker_rating_a)}}}function Y(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const Z=500,ee=Object.keys(u).filter(e=>"unknown"!==e);class te extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const n=this._config,s=this._createHeader(t("sidepanel.graph_settings"),t("sidepanel.global_defaults"));e.appendChild(s);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=n.graphSettings,l=n.topology,d=c?.global_horizon??i,h=c?.circuits??{},p=document.createElement("div");p.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=t("sidepanel.graph_horizon"),p.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=t("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),p.appendChild(g),a.appendChild(p),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.circuit_scales"),e.appendChild(n);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=h[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${n}`,Z,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t("sidepanel.subdevice_scales"),e.appendChild(n);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=s.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[n]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=n;for(const e of Object.keys(o)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${n}`,Z,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:n,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=t("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:n}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${m(String(t.breaker_rating_a))}A · ${m(String(t.voltage))}V · Tabs [${m(String(t.tabs))}]`,i=this._createHeader(m(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(m(t.name),m(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,n){if(!1===n.is_user_controllable||!n.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=n.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${t("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,n){if(!n.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=t("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=n.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of ee){const t=document.createElement("option");t.value=e,t.textContent=u[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${t("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,n){const s=document.createElement("div");s.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=t("sidepanel.graph_horizon"),s.appendChild(a);const r=n.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||i,d=r?.globalHorizon||i,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:t("sidepanel.global")}];for(const e of Object.keys(o))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=n.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${t("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}s.appendChild(h),e.appendChild(s)}_renderMonitoringSection(e,n){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=t("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=n.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,l.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(t("sidepanel.continuous_pct"),"continuous",u,n)),p.appendChild(this._createThresholdRow(t("sidepanel.spike_pct"),"spike",g,n)),p.appendChild(this._createDurationRow(t("sidepanel.window_duration"),"window-m",_,1,180,"m",n)),p.appendChild(this._createDurationRow(t("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",n)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=n.entities?.power||n.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=h.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=n.entities?.power||n.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${t("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,n,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${n}`,r.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,n,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(s),p.value=String(i),p.dataset.role=`threshold-${n}`,c&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,h.appendChild(p),h.appendChild(u),c||p.addEventListener("input",()=>{this._debounce(`threshold-${n}`,Z,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),n=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${t("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(h),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(s,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",te);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new A,this._graphSettingsCache=new S,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._resizeObserver=null,this._lastCardWidth=0,this._resizeDebounce=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)o[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await J(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return f(this._config)}set hass(e){if(this._hass=e,this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(e).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${t("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:n,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,n){if(!n)throw new Error(t("card.device_not_found"));const i=await e.callWS({type:`${s}/panel_topology`,device_id:n}),o=i.panel_size||Y(i.circuits);if(!o)throw new Error(t("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===n)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,n){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),a=i.find(e=>e.id===n)||null;if(!a)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===n),c=i.filter(e=>e.via_device_id===n),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),h={},p=a.name_by_user||a.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");h[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(a.identifiers)for(const e of a.identifiers)e[0]===s&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Y(h)),!g)throw new Error(t("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:a.sw_version||"",panel_size:g,device_id:n,device_name:a.name_by_user||a.name||t("header.default_name"),circuits:h,sub_devices:_},panelDevice:a,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}}catch{}try{await J(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const s=this._horizonMap?.get(t)||i;if(!o[s]?.useRealtime)continue;const a=C(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=v(s),d=b(l),h=e-l;y(this._powerHistory,t,c,e,h,d)}for(const{entityId:t,key:n,devId:s}of X(this._topology)){const a=this._subDeviceHorizonMap?.get(s)||i;if(!o[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=v(a),d=b(l),h=e-l;y(this._powerHistory,n,c,e,h,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Q(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=f(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=F(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${k(i)} ${E(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=p.power;n.endsWith("_soc")?c=p.soc:n.endsWith("_soe")&&(c=p.soe);const l=!!e.closest(".bess-chart-col");K(e,t,r,s?.has(i)?v(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const o=t.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const s=this._graphSettingsCache.settings,a=s?.global_horizon||i,r=s?.circuits?.[o]?{...s.circuits[o],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:o,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings,o=t?.global_horizon||i,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:o}:{horizon:o,has_override:!1,globalHorizon:o};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._horizonMap.set(t,o)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],o=n?.has_override?n.horizon:e.global_horizon||i;this._subDeviceHorizonMap.set(t,o)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_invalidateCharts(){for(const e of this.shadowRoot.querySelectorAll(".chart-container")){const t=e.querySelector("ha-chart-base");t&&t.remove()}}_setupResizeObserver(){this._resizeObserver&&this._resizeObserver.disconnect();const e=this.shadowRoot.querySelector("ha-card");e&&(this._lastCardWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const t=e[0];if(!t)return;const n=t.contentRect.width;Math.abs(n-this._lastCardWidth)<5||(this._lastCardWidth=n,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{this._invalidateCharts(),this._updateDOM()},150))}),this._resizeObserver.observe(e))}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?t("card.loading"):t("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${m(e)}\n
\n
\n `)}const n=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,n){const i=m(e.device_name||t("header.default_name")),o=m(e.serial||""),s=m(e.firmware||""),a="current"===(n.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,h=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${t("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${t("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${t("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${t("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${t("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${t("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${t("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(u).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(n,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const n=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...n,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${t("status.monitoring")} · ${n.length} ${t("status.circuits")} · ${i.length} ${t("status.mains")}\n \n ${s>0?`${s} ${t(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${t(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${t(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":N(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=M(Math.max(...n));0===P(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let h="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),p=a.get(n);if(h+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);h+=L(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),h+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(h+=D(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);h+=L(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(h+=D(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);h+=L(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}h+=`
${n}
`}return h}(n,i,0,e,this._config,s),d=function(e,n,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===c&&!o||e.type===l&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===l).length;let d=0,h="";for(const[e,o]of a){const s=o.type===l?t("subdevice.ev_charger"):o.type===c?t("subdevice.battery"):t("subdevice.fallback"),a=F(o),p=a?n.states[a]:null,u=p&&parseFloat(p.state)||0,g=o.type===c,_=o.type===l,f=g?j(o):null,v=g?G(o):null,b=g?q(o):null,y=W(o,n,i,new Set([a,f,v,b].filter(Boolean))),w=V(e,0,g,a,f,v);let x="";g?x="sub-device-bess":_&&(d++,d===r&&r%2==1&&(x="sub-device-full")),h+=`\n
\n
\n ${m(s)}\n ${m(o.name||"")}\n ${a?`${k(u)} ${E(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return h}(n,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${d?`
${d}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const h=this.shadowRoot.querySelector(".slide-confirm");if(h){this._bindSlideConfirm(h,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM(),this._setupResizeObserver()}}class ie extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===s)&&!e.via_device_id).map(e=>{const n=(e.identifiers||[]).find(e=>e[0]===s)?.[1]||"",i=e.name_by_user||e.name||t("editor.panel_label");return{device_id:e.id,label:`${i} (${n})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n;const c=document.createElement("option");if(c.value="",c.textContent=t("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=n+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,h=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",t("editor.days")),g=l(h,"0","23",t("editor.hours")),_=l(p,"0","59",t("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,n,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=t("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=n,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,n,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=t("editor.visible_sections"),s.style.cssText=n,o.appendChild(s);const a=[{key:"show_panel",label:t("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:t("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:t("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${s}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||n;e.innerHTML="";for(const[n,i]of Object.entries(h)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||n),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.get("span-panel-card")||customElements.define("span-panel-card",ne),customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",ie),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",h="sub_",p={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},_="#ff9800",m={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>m[e])}function v(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function b(e){const t=s[e];return t?t.ms:s[o].ms}function y(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function w(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function x(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function C(e){return p[e.chart_metric]||p[i]}function S(e,t){const n=function(e){return C(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class z{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const E=p.power;function k(e){return E.unit(e)}function $(e){return(e<0?"-":"")+E.format(e)}function M(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function N(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":N(t)===N(n)?"col-span":"row-span"}class L{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=o?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function D(e,t,i,o,s,a,l,d,h,p){const u=t.entities?.power,m=u?l.states[u]:null,v=m&&parseFloat(m.state)||0,b=t.device_type===c||v<0,y=t.entities?.switch,w=y?l.states[y]:null,x=w?"on"===w.state:(m?.attributes?.relay_state||t.relay_state)===r,S=t.breaker_rating_a,z=S?`${Math.round(S)}A`:"",E=f(t.name||n("grid.unknown")),M=C(d);let P;if("current"===M.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${M.format(i)}A`}else P=`${$(v)}${k(v)}`;const N=g[p||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),D=L?_:"#555",T=``;let R="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${E}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(x?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const R={names:["power","battery power"],suffixes:["_power"]},H={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},O={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,R)}function G(e){return F(e,H)}function q(e){return F(e,I)}function W(e){return F(e,O)}function V(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${h}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${h}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${h}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function X(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=y(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,x(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=G(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${h}${n}_${i}`,devId:n})}return t}async function K(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=S(i,n);if(!t)continue;let s;s=o&&o.has(e)?b(o.get(e)):v(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of J(t)){let t;t=s&&s.has(o)?b(s.get(o)):v(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(U(e,n.entityIds,n.uuidByEntity,t,i)):r.push(X(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,h=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=n.fixedMin,m.max=n.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===n.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(n,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=v(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=M(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=M(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=M(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=M(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const h=C(o),p="current"===h.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,_=u?t.states[u]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===c||m<0,v=d.entities?.switch,y=v?t.states[v]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${h.format(i)}A`}else x.innerHTML=`${$(m)}${k(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=n(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const z=g[S]||g.unknown,E=i.querySelector(".shedding-icon");E&&(E.setAttribute("icon",z.icon),E.style.color=z.color,E.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(N,t,e,a?.has(o)?b(a.get(o)):l,h,f,n,d.breaker_rating_a)}}}function Z(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const ee=500,te=Object.keys(g).filter(e=>"unknown"!==e);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,h=c?.circuits??{},p=document.createElement("div");p.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),p.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=n("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),p.appendChild(g),a.appendChild(p),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=h[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,ee,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,ee,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}i.appendChild(h),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of te){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}i.appendChild(h),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,l.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",_,1,180,"m",t)),p.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",t)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=h.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,ee,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(s),p.value=String(i),p.dataset.role=`threshold-${t}`,c&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,h.appendChild(p),h.appendChild(u),c||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,ee,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(h),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ne);class ie extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new L,this._graphSettingsCache=new z,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._resizeObserver=null,this._lastCardWidth=0,this._resizeDebounce=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)s[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await K(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return v(this._config)}get _configEntryId(){return this._panelDevice?.config_entries?.[0]||null}set hass(i){var o;if(this._hass=i,o=i?.language,e=t[o]?o:"en",this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(i,this._configEntryId).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${n("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||Z(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,t){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=i.find(e=>e.id===t)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===t),c=i.filter(e=>e.via_device_id===t),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),h={},p=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");h[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(s.identifiers)for(const e of s.identifiers)e[0]===a&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Z(h)),!g)throw new Error(n("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:s.sw_version||"",panel_size:g,device_id:t,device_name:s.name_by_user||s.name||n("header.default_name"),circuits:h,sub_devices:_},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._horizonMap.set(t,i)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._subDeviceHorizonMap.set(t,i)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=S(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=b(i),d=y(l),h=e-l;w(this._powerHistory,t,c,e,h,d)}for(const{entityId:t,key:n,devId:i}of J(this._topology)){const a=this._subDeviceHorizonMap?.get(i)||o;if(!s[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=b(a),d=y(l),h=e-l;w(this._powerHistory,n,c,e,h,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Y(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=v(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=j(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${k(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?b(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass,this._configEntryId),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const i=t.dataset.uuid;if(i&&this._topology){const e=this._topology.circuits[i];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const s=this._graphSettingsCache.settings,a=s?.global_horizon||o,r=s?.circuits?.[i]?{...s.circuits[i],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:i,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const t=this._graphSettingsCache.settings,i=t?.global_horizon||o,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._horizonMap.set(t,i)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._subDeviceHorizonMap.set(t,i)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_invalidateCharts(){for(const e of this.shadowRoot.querySelectorAll(".chart-container")){const t=e.querySelector("ha-chart-base");t&&t.remove()}}_setupResizeObserver(){this._resizeObserver&&this._resizeObserver.disconnect();const e=this.shadowRoot.querySelector("ha-card");e&&(this._lastCardWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const t=e[0];if(!t)return;const n=t.contentRect.width;Math.abs(n-this._lastCardWidth)<5||(this._lastCardWidth=n,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{this._invalidateCharts(),this._updateDOM()},150))}),this._resizeObserver.observe(e))}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?n("card.loading"):n("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const t=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,h=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(t,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===N(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let h="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),p=a.get(n);if(h+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);h+=D(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),h+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(h+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);h+=D(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(h+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);h+=D(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}h+=`
${n}
`}return h}(t,i,0,e,this._config,s),c=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,h="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=j(o),p=a?t.states[a]:null,u=p&&parseFloat(p.state)||0,g=o.type===l,_=o.type===d,m=g?G(o):null,v=g?q(o):null,b=g?W(o):null,y=V(o,t,i,new Set([a,m,v,b].filter(Boolean))),w=B(e,0,g,a,m,v);let x="";g?x="sub-device-bess":_&&(c++,c===r&&r%2==1&&(x="sub-device-full")),h+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${k(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return h}(t,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${c?`
${c}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const h=this.shadowRoot.querySelector(".slide-confirm");if(h){this._bindSlideConfirm(h,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM(),this._setupResizeObserver()}}class oe extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===a)&&!e.via_device_id).map(e=>{const t=(e.identifiers||[]).find(e=>e[0]===a)?.[1]||"",i=e.name_by_user||e.name||n("editor.panel_label");return{device_id:e.id,label:`${i} (${t})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=t;const c=document.createElement("option");if(c.value="",c.textContent=n("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=t+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,h=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",n("editor.days")),g=l(h,"0","23",n("editor.hours")),_=l(p,"0","59",n("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=t,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,t,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=n("editor.visible_sections"),s.style.cssText=t,o.appendChild(s);const a=[{key:"show_panel",label:n("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:n("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:n("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${a}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.get("span-panel-card")||customElements.define("span-panel-card",ie),customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",oe),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.9.0 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index a67b0f6..fe7ce38 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=500,v=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,b,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,b,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function k(e){return Math.ceil(e/2)}function C(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return k(t)===k(n)?"row-span":C(t)===C(n)?"col-span":"row-span"}function M(e){return h[e.chart_metric]||h[i]}function P(e,t){const n=function(e){return M(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e){const t=Date.now();if(this._fetching)return this._status;if(this._status&&t-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const n=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:{},return_response:!0});this._status=n?.response||null,this._lastFetch=t}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,s,a,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===c||b<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,k=z?`${Math.round(z)}A`:"",C=f(t.name||n("grid.unknown")),E=M(d);let P;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${E.format(i)}A`}else P=`${$(b)}${S(b)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${k?`${k}`:""}\n ${C}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function R(e){return q(e,T)}function j(e){return q(e,D)}function O(e){return q(e,I)}function G(e){return q(e,H)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function W(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=s[e];return t?t.ms:s[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=h[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=M(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const k=g[z]||g.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",k.icon),C.style.color=k.color,C.title=k.label());const E=i.querySelector(".shedding-icon-secondary");E&&(k.icon2?(E.setAttribute("icon",k.icon2),E.style.color=k.color,E.style.display=""):E.style.display="none");const M=i.querySelector(".shedding-label");M&&(k.textLabel?(M.textContent=k.textLabel,M.style.color=k.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".chart-container");if(P){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(P,t,e,a?.has(o)?V(a.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,s){if(!n.sub_devices)return;const a=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=R(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?V(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:R(i)};i.type===l&&(e.soc=j(i),e.soe=O(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=P(i,n);if(!t)continue;let s;s=o&&o.has(e)?V(o.get(e)):B(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=s&&s.has(o)?V(s.get(o)):B(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class se{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null,this._resizeObserver=null,this._lastContainerWidth=0,this._resizeDebounce=null,this._container=null}async render(e,t,i,a){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=a,this._container=e;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t),await this._graphSettingsCache.fetch(t);const r=this._topology;this._horizonMap=new Map;const c=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const t=c?.circuits?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._horizonMap.set(e,n)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const t=c?.sub_devices?.[e],n=t?.has_override?t.horizon:c?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const p=Math.ceil(this._panelSize/2),h=(B(a),this._monitoringCache.status),u=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(r,a),m=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(h),_=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=k(Math.max(...n));0===C(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=A(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=A(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(r,p,0,t,a,h),b=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=R(o),h=a?t.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?j(o):null,b=g?O(o):null,v=g?G(o):null,y=F(o,t,i,new Set([a,_,b,v].filter(Boolean))),x=W(e,0,g,a,_,b);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(r,t,a);e.innerHTML=`\n \n ${u}\n ${m}\n ${b?`
${b}
`:""}\n ${!1!==a.show_panel?`\n
\n ${_}\n
\n `:""}\n \n `,this._bindGearClicks(e,r),this._bindToggleClicks(e,r),e.addEventListener("side-panel-closed",()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()}),e.addEventListener("graph-settings-changed",async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass);const t=this._graphSettingsCache.settings;if(r?.circuits)for(const e of Object.keys(r.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(r?.sub_devices)for(const e of Object.keys(r.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,r,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)});try{await ie(t,r,a,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,r,a,this._powerHistory,this._horizonMap),Z(e,t,r,a,this._powerHistory,this._subDeviceHorizonMap);const v=e.querySelector(".slide-confirm");v&&(this._bindSlideConfirm(v,e),e.classList.add("switches-disabled")),this._setupResizeObserver(e,r,a),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,r,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)s[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,r,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=P(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const s=i.closest("[data-uuid]");if(!s||!t||!this._hass)return;const a=s.dataset.uuid,r=t.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const s=e.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass),void s.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&t){const e=t.circuits[a];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[a]?{...n.circuits[a],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({...e,uuid:a,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,a=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:a})}})}_setupResizeObserver(e,t,n){this._resizeObserver&&this._resizeObserver.disconnect(),this._lastContainerWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(i=>{const o=i[0];if(!o)return;const s=o.contentRect.width;Math.abs(s-this._lastContainerWidth)<5||(this._lastContainerWidth=s,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}Y(e,this._hass,t,n,this._powerHistory,this._horizonMap),Z(e,this._hass,t,n,this._powerHistory,this._subDeviceHorizonMap)},150))}),this._resizeObserver.observe(e)}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const s=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=s.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=s.notification_title_template||"SPAN: {name} {alert_type}",m=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),k=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${s?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,s=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${k}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const C=e.querySelector("#toggle-all-circuits");C&&!S&&$&&(C.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),s=e.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){s.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none";const a=e.querySelector("#global-status");try{if(s){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(a&&(a.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,a.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",a)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),s=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const a=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);s.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),a=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),a&&a.addEventListener("input",()=>{r("notification_message_template",a.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const s=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[l]:o,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,s="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(a,s,r),await this.render(e,t)})}}function ue(e){return Object.keys(s).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}async render(e,t,i,s){let r;void 0!==i&&(this._configEntryId=i),void 0!==s&&(this._deviceId=s);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${a}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},s=o.horizon??l,a=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${a?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${a}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${a}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:a,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:a,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:a,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new se,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry()}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this._hass?.connection&&(this._deviceRegistryUnsub=this._hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this._hass||!this._discovered)return;const e=(await this._hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=new Set(this._panels.map(e=>e.id)),n=new Set(e.map(e=>e.id));t.size===n.size&&[...t].every(e=>n.has(e))||(this._panels=e,!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0&&(this._selectedPanelId=this._panels[0].id,localStorage.setItem("span_panel_selected",this._selectedPanelId)),this._render())}set hass(e){const t=!this._hass&&e;this._hass=e,this._dashboardTab._hass=e;const n=this.shadowRoot.querySelector("ha-menu-button");n&&(n.hass=e),this._discovered||this._discoverPanels(),t&&this._subscribeDeviceRegistry()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en",this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n \n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const o=this.shadowRoot.querySelector("ha-menu-button");o&&(o.hass=this._hass,o.narrow=this._narrow);const s=this.shadowRoot.getElementById("panel-select");s&&s.addEventListener("change",()=>{this._selectedPanelId=s.value,localStorage.setItem("span_panel_selected",s.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig();await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.get("span-panel")||customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.8.9 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=500,v=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,b,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,b,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function C(e){return Math.ceil(e/2)}function k(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return C(t)===C(n)?"row-span":k(t)===k(n)?"col-span":"row-span"}function P(e){return h[e.chart_metric]||h[i]}function M(e,t){const n=function(e){return P(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=o?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,s,a,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===c||b<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,C=z?`${Math.round(z)}A`:"",k=f(t.name||n("grid.unknown")),E=P(d);let M;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${E.format(i)}A`}else M=`${$(b)}${S(b)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${k}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function R(e){return q(e,T)}function j(e){return q(e,D)}function G(e){return q(e,I)}function O(e){return q(e,H)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function W(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=s[e];return t?t.ms:s[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=h[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=P(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const C=g[z]||g.unknown,k=i.querySelector(".shedding-icon");k&&(k.setAttribute("icon",C.icon),k.style.color=C.color,k.title=C.label());const E=i.querySelector(".shedding-icon-secondary");E&&(C.icon2?(E.setAttribute("icon",C.icon2),E.style.color=C.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(C.textLabel?(P.textContent=C.textLabel,P.style.color=C.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(M,t,e,a?.has(o)?V(a.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,s){if(!n.sub_devices)return;const a=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=R(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?V(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:R(i)};i.type===l&&(e.soc=j(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=M(i,n);if(!t)continue;let s;s=o&&o.has(e)?V(o.get(e)):B(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=s&&s.has(o)?V(s.get(o)):B(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class se{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null,this._resizeObserver=null,this._lastContainerWidth=0,this._resizeDebounce=null,this._container=null}async render(e,t,i,a,r){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=a,this._container=e,this._configEntryId=r||null;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t,this._configEntryId),await this._graphSettingsCache.fetch(t,this._configEntryId);const c=this._topology;this._horizonMap=new Map;const p=this._graphSettingsCache.settings;if(c?.circuits)for(const e of Object.keys(c.circuits)){const t=p?.circuits?.[e],n=t?.has_override?t.horizon:p?.global_horizon||o;this._horizonMap.set(e,n)}if(c?.sub_devices)for(const e of Object.keys(c.sub_devices)){const t=p?.sub_devices?.[e],n=t?.has_override?t.horizon:p?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const h=Math.ceil(this._panelSize/2),u=(B(a),this._monitoringCache.status),m=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(c,a),_=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),b=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=C(Math.max(...n));0===k(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=A(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=A(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(c,h,0,t,a,u),v=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=R(o),h=a?t.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?j(o):null,b=g?G(o):null,v=g?O(o):null,y=F(o,t,i,new Set([a,_,b,v].filter(Boolean))),x=W(e,0,g,a,_,b);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(c,t,a);e.innerHTML=`\n \n ${m}\n ${_}\n ${v?`
${v}
`:""}\n ${!1!==a.show_panel?`\n
\n ${b}\n
\n `:""}\n \n `,this._bindGearClicks(e,c),this._bindToggleClicks(e,c),this._onSidePanelClosed=()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()},this._onGraphSettingsChanged=async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const t=this._graphSettingsCache.settings;if(c?.circuits)for(const e of Object.keys(c.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(c?.sub_devices)for(const e of Object.keys(c.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,c,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,c,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,c,this._config,this._powerHistory,this._subDeviceHorizonMap)},e.addEventListener("side-panel-closed",this._onSidePanelClosed),e.addEventListener("graph-settings-changed",this._onGraphSettingsChanged);try{await ie(t,c,a,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,c,a,this._powerHistory,this._horizonMap),Z(e,t,c,a,this._powerHistory,this._subDeviceHorizonMap);const y=e.querySelector(".slide-confirm");y&&(this._bindSlideConfirm(y,e),e.classList.add("switches-disabled")),this._setupResizeObserver(e,c,a),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,c,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,c,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)s[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,c,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=M(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const s=i.closest("[data-uuid]");if(!s||!t||!this._hass)return;const a=s.dataset.uuid,r=t.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const s=e.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass,this._configEntryId),void s.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&t){const e=t.circuits[a];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[a]?{...n.circuits[a],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({...e,uuid:a,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,a=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:a})}})}_setupResizeObserver(e,t,n){this._resizeObserver&&this._resizeObserver.disconnect(),this._lastContainerWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(i=>{const o=i[0];if(!o)return;const s=o.contentRect.width;Math.abs(s-this._lastContainerWidth)<5||(this._lastContainerWidth=s,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}Y(e,this._hass,t,n,this._powerHistory,this._horizonMap),Z(e,this._hass,t,n,this._powerHistory,this._subDeviceHorizonMap)},150))}),this._resizeObserver.observe(e)}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null),this._container&&this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._container&&this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const s=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=s.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=s.notification_title_template||"SPAN: {name} {alert_type}",m=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),C=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${s?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,s=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${C}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const k=e.querySelector("#toggle-all-circuits");k&&!S&&$&&(k.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),s=e.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){s.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none";const a=e.querySelector("#global-status");try{if(s){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(a&&(a.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,a.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",a)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),s=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const a=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);s.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),a=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),a&&a.addEventListener("input",()=>{r("notification_message_template",a.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const s=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[l]:o,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,s="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(a,s,r),await this.render(e,t)})}}function ue(e){return Object.keys(s).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}stop(){for(const e of this._debounceTimers.values())clearTimeout(e);this._debounceTimers.clear()}async render(e,t,i,s){let r;void 0!==i&&(this._configEntryId=i),void 0!==s&&(this._deviceId=s);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${a}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},s=o.horizon??l,a=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${a?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${a}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${a}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:a,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:a,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:a,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new se,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._monitoringTab.stop(),this._settingsTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry()}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this._hass?.connection&&(this._deviceRegistryUnsub=this._hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this._hass||!this._discovered)return;const e=(await this._hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=new Set(this._panels.map(e=>e.id)),n=new Set(e.map(e=>e.id));t.size===n.size&&[...t].every(e=>n.has(e))||(this._panels=e,!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0&&(this._selectedPanelId=this._panels[0].id,localStorage.setItem("span_panel_selected",this._selectedPanelId)),this._render())}set hass(e){const t=!this._hass&&e;this._hass=e,this._dashboardTab._hass=e;const n=this.shadowRoot.querySelector("ha-menu-button");n&&(n.hass=e),this._discovered||this._discoverPanels(),t&&this._subscribeDeviceRegistry()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en",this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n \n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const o=this.shadowRoot.querySelector("ha-menu-button");o&&(o.hass=this._hass,o.narrow=this._narrow);const s=this.shadowRoot.getElementById("panel-select");s&&s.addEventListener("change",()=>{this._selectedPanelId=s.value,localStorage.setItem("span_panel_selected",s.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig(),n=this._panels.find(e=>e.id===this._selectedPanelId),i=n?.config_entries?.[0]||null;await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t,i);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.get("span-panel")||customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.9.0 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js index 1bb5876..ef0e223 100644 --- a/src/card/span-panel-card.js +++ b/src/card/span-panel-card.js @@ -1,5 +1,5 @@ import { DEFAULT_CHART_METRIC, DEFAULT_GRAPH_HORIZON, GRAPH_HORIZONS, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; -import { t } from "../i18n.js"; +import { setLanguage, t } from "../i18n.js"; import { escapeHtml } from "../helpers/sanitize.js"; import { getHistoryDurationMs, getHorizonDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; @@ -135,8 +135,13 @@ export class SpanPanelCard extends HTMLElement { return getHistoryDurationMs(this._config); } + get _configEntryId() { + return this._panelDevice?.config_entries?.[0] || null; + } + set hass(hass) { this._hass = hass; + setLanguage(hass?.language); if (!this._config.device_id) { this.shadowRoot.innerHTML = ` @@ -154,7 +159,7 @@ export class SpanPanelCard extends HTMLElement { this._discovering = false; this._render(); this._loadHistory(); - this._monitoringCache.fetch(hass).then(() => { + this._monitoringCache.fetch(hass, this._configEntryId).then(() => { if (this._rendered) this._updateDOM(); }); }); @@ -217,7 +222,7 @@ export class SpanPanelCard extends HTMLElement { // Fetch graph settings and build horizon map try { - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); const settings = this._graphSettingsCache.settings; if (settings && this._topology?.circuits) { for (const uuid of Object.keys(this._topology.circuits)) { @@ -426,7 +431,7 @@ export class SpanPanelCard extends HTMLElement { sidePanel.hass = this._hass; if (gearBtn.classList.contains("panel-gear")) { - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); sidePanel.open({ panelMode: true, topology: this._topology, @@ -441,7 +446,7 @@ export class SpanPanelCard extends HTMLElement { if (circuit) { const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); const graphSettings = this._graphSettingsCache.settings; const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; const graphHorizonInfo = graphSettings?.circuits?.[uuid] @@ -462,7 +467,7 @@ export class SpanPanelCard extends HTMLElement { if (subDevId && this._topology?.sub_devices?.[subDevId]) { const sub = this._topology.sub_devices[subDevId]; - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); const graphSettings = this._graphSettingsCache.settings; const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; const graphHorizonInfo = graphSettings?.sub_devices?.[subDevId] @@ -483,7 +488,7 @@ export class SpanPanelCard extends HTMLElement { async _onGraphSettingsChanged() { this._graphSettingsCache.invalidate(); - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); // Rebuild horizon map const settings = this._graphSettingsCache.settings; diff --git a/src/constants.js b/src/constants.js index e7dedc0..8060b2b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,6 +1,6 @@ import { t } from "./i18n.js"; -export const CARD_VERSION = "0.8.9"; +export const CARD_VERSION = "0.9.0"; // ── Defaults ──────────────────────────────────────────────────────────────── diff --git a/src/core/header-renderer.js b/src/core/header-renderer.js index 80172c8..8a6bde1 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.js @@ -27,7 +27,7 @@ export function buildHeaderHTML(topology, config) {

${panelName}

${serial} -
diff --git a/src/core/monitoring-status.js b/src/core/monitoring-status.js index dab4c07..bf12b57 100644 --- a/src/core/monitoring-status.js +++ b/src/core/monitoring-status.js @@ -20,7 +20,7 @@ export class MonitoringStatusCache { * @param {object} hass - Home Assistant instance * @returns {Promise} Monitoring status or null */ - async fetch(hass) { + async fetch(hass, configEntryId) { const now = Date.now(); if (this._fetching) return this._status; if (this._status && now - this._lastFetch < MONITORING_POLL_INTERVAL_MS) { @@ -29,11 +29,13 @@ export class MonitoringStatusCache { this._fetching = true; try { + const serviceData = {}; + if (configEntryId) serviceData.config_entry_id = configEntryId; const resp = await hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, service: "get_monitoring_status", - service_data: {}, + service_data: serviceData, return_response: true, }); this._status = resp?.response || null; diff --git a/src/core/side-panel.js b/src/core/side-panel.js index 04a399a..6a50ddc 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.js @@ -313,7 +313,7 @@ class SpanSidePanel extends HTMLElement { for (const key of Object.keys(GRAPH_HORIZONS)) { const opt = document.createElement("option"); opt.value = key; - opt.textContent = key; + opt.textContent = t(`horizon.${key}`) || key; if (key === globalHorizon) opt.selected = true; globalSelect.appendChild(opt); } @@ -358,7 +358,7 @@ class SpanSidePanel extends HTMLElement { for (const key of Object.keys(GRAPH_HORIZONS)) { const opt = document.createElement("option"); opt.value = key; - opt.textContent = key; + opt.textContent = t(`horizon.${key}`) || key; if (key === effectiveHorizon) opt.selected = true; select.appendChild(opt); } @@ -439,7 +439,7 @@ class SpanSidePanel extends HTMLElement { for (const key of Object.keys(GRAPH_HORIZONS)) { const opt = document.createElement("option"); opt.value = key; - opt.textContent = key; + opt.textContent = t(`horizon.${key}`) || key; if (key === effectiveHorizon) opt.selected = true; select.appendChild(opt); } diff --git a/src/i18n.js b/src/i18n.js index b694b4e..f18da0c 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -86,6 +86,7 @@ const translations = { // Header "header.default_name": "SPAN Panel", "header.monitoring_settings": "Panel monitoring settings", + "header.graph_settings": "Graph time horizon settings", "header.site": "Site", "header.grid": "Grid", "header.upstream": "Upstream", @@ -241,6 +242,7 @@ const translations = { "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "Configuraci\u00f3n de monitoreo del panel", + "header.graph_settings": "Configuraci\u00f3n del horizonte temporal del gr\u00e1fico", "header.site": "Sitio", "header.grid": "Red", "header.upstream": "Aguas arriba", @@ -382,6 +384,7 @@ const translations = { "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "Param\u00e8tres de surveillance du panneau", + "header.graph_settings": "Param\u00e8tres d'horizon temporel du graphique", "header.site": "Site", "header.grid": "R\u00e9seau", "header.upstream": "Amont", @@ -525,6 +528,7 @@ const translations = { "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "\u30d1\u30cd\u30eb\u30e2\u30cb\u30bf\u30ea\u30f3\u30b0\u8a2d\u5b9a", + "header.graph_settings": "\u30b0\u30e9\u30d5\u6642\u9593\u7bc4\u56f2\u8a2d\u5b9a", "header.site": "\u30b5\u30a4\u30c8", "header.grid": "\u30b0\u30ea\u30c3\u30c9", "header.upstream": "\u4e0a\u6d41", @@ -670,6 +674,7 @@ const translations = { "sidepanel.clear_graph_horizon_failed": "Clear graph horizon failed:", "header.default_name": "SPAN Panel", "header.monitoring_settings": "Configura\u00e7\u00f5es de monitoramento do painel", + "header.graph_settings": "Configura\u00e7\u00f5es do horizonte temporal do gr\u00e1fico", "header.site": "Local", "header.grid": "Rede", "header.upstream": "Montante", diff --git a/src/panel/span-panel.js b/src/panel/span-panel.js index 2ca92f9..069e600 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.js @@ -110,6 +110,8 @@ export class SpanPanelElement extends HTMLElement { disconnectedCallback() { this._dashboardTab.stop(); + this._monitoringTab.stop(); + this._settingsTab.stop(); if (this._onVisibilityChange) { document.removeEventListener("visibilitychange", this._onVisibilityChange); this._onVisibilityChange = null; @@ -320,7 +322,9 @@ export class SpanPanelElement extends HTMLElement { case "dashboard": { container.innerHTML = ""; const config = this._buildDashboardConfig(); - await this._dashboardTab.render(container, this._hass, this._selectedPanelId, config); + const dashDevice = this._panels.find(p => p.id === this._selectedPanelId); + const dashEntryId = dashDevice?.config_entries?.[0] || null; + await this._dashboardTab.render(container, this._hass, this._selectedPanelId, config, dashEntryId); break; } case "monitoring": { diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js index 7d45390..f83aa03 100644 --- a/src/panel/tab-dashboard.js +++ b/src/panel/tab-dashboard.js @@ -31,12 +31,13 @@ export class DashboardTab { this._container = null; } - async render(container, hass, deviceId, config) { + async render(container, hass, deviceId, config, configEntryId) { this.stop(); this._hass = hass; this._powerHistory.clear(); this._config = config; this._container = container; + this._configEntryId = configEntryId || null; try { const result = await discoverTopology(hass, deviceId); @@ -47,8 +48,8 @@ export class DashboardTab { return; } - await this._monitoringCache.fetch(hass); - await this._graphSettingsCache.fetch(hass); + await this._monitoringCache.fetch(hass, this._configEntryId); + await this._graphSettingsCache.fetch(hass, this._configEntryId); const topo = this._topology; @@ -98,13 +99,13 @@ export class DashboardTab { this._bindGearClicks(container, topo); this._bindToggleClicks(container, topo); - container.addEventListener("side-panel-closed", () => { + this._onSidePanelClosed = () => { this._monitoringCache.invalidate(); this._graphSettingsCache.invalidate(); - }); - container.addEventListener("graph-settings-changed", async () => { + }; + this._onGraphSettingsChanged = async () => { this._graphSettingsCache.invalidate(); - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); // Rebuild horizon map const newSettings = this._graphSettingsCache.settings; @@ -134,7 +135,9 @@ export class DashboardTab { } updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory, this._subDeviceHorizonMap); - }); + }; + container.addEventListener("side-panel-closed", this._onSidePanelClosed); + container.addEventListener("graph-settings-changed", this._onGraphSettingsChanged); try { await loadHistory(hass, topo, config, this._powerHistory, this._horizonMap, this._subDeviceHorizonMap); @@ -339,7 +342,7 @@ export class DashboardTab { sidePanel.hass = this._hass; if (gearBtn.classList.contains("panel-gear")) { - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); sidePanel.open({ panelMode: true, topology, @@ -356,7 +359,7 @@ export class DashboardTab { await this._monitoringCache.fetch(this._hass); const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); const graphSettings = this._graphSettingsCache.settings; const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; const graphHorizonInfo = graphSettings?.circuits?.[uuid] @@ -377,7 +380,7 @@ export class DashboardTab { if (subDevId && topology?.sub_devices?.[subDevId]) { const sub = topology.sub_devices[subDevId]; - await this._graphSettingsCache.fetch(this._hass); + await this._graphSettingsCache.fetch(this._hass, this._configEntryId); const graphSettings = this._graphSettingsCache.settings; const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; const graphHorizonInfo = graphSettings?.sub_devices?.[subDevId] @@ -437,5 +440,13 @@ export class DashboardTab { clearTimeout(this._resizeDebounce); this._resizeDebounce = null; } + if (this._container && this._onSidePanelClosed) { + this._container.removeEventListener("side-panel-closed", this._onSidePanelClosed); + this._onSidePanelClosed = null; + } + if (this._container && this._onGraphSettingsChanged) { + this._container.removeEventListener("graph-settings-changed", this._onGraphSettingsChanged); + this._onGraphSettingsChanged = null; + } } } diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.js index 28da305..b97b3e8 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.js @@ -46,6 +46,17 @@ export class MonitoringTab { this._configEntryId = null; } + stop() { + if (this._notifyCloseHandler) { + document.removeEventListener("click", this._notifyCloseHandler); + this._notifyCloseHandler = null; + } + if (this._debounceTimer) { + clearTimeout(this._debounceTimer); + this._debounceTimer = null; + } + } + async render(container, hass, configEntryId) { if (configEntryId !== undefined) this._configEntryId = configEntryId; if (this._notifyCloseHandler) { diff --git a/src/panel/tab-settings.js b/src/panel/tab-settings.js index 5b2fa5d..99eeb07 100644 --- a/src/panel/tab-settings.js +++ b/src/panel/tab-settings.js @@ -4,7 +4,7 @@ import { t } from "../i18n.js"; function horizonOptions(selectedKey) { return Object.keys(GRAPH_HORIZONS) - .map(key => ``) + .map(key => ``) .join(""); } @@ -22,6 +22,13 @@ export class SettingsTab { this._deviceId = null; } + stop() { + for (const timer of this._debounceTimers.values()) { + clearTimeout(timer); + } + this._debounceTimers.clear(); + } + async render(container, hass, configEntryId, deviceId) { if (configEntryId !== undefined) this._configEntryId = configEntryId; if (deviceId !== undefined) this._deviceId = deviceId; From c1bbea2f9ff79c5abb0be50b883db3eb4ade262c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:28:12 -0700 Subject: [PATCH 078/101] refactor: migrate to strict TypeScript, extract DashboardController, fix XSS - Convert all 28 source files from JS to strict TypeScript with full type coverage (strict, noUncheckedIndexedAccess, noUnusedLocals/Parameters) - Create shared types module (src/types.ts) with 25+ interfaces - Extract DashboardController to eliminate duplicated logic between SpanPanelCard and DashboardTab (slide-to-confirm, sample recording, horizon maps, toggle/gear clicks, recorder refresh, resize observer) - Fix XSS: escape panel names in option tags (span-panel.ts), escape title/subtitle in side panel header (side-panel.ts) - Fix side panel _callDomainService to use callWS with return_response - Remove dead code: getMainsMonitoringInfo, formatPower, unused params - Wire up getEffectiveHorizon/getEffectiveSubDeviceHorizon (replacing 5 duplicate horizon map construction blocks) - Replace hist.shift() loop with findIndex+splice for O(1) pruning - Replace Math.max(...spread) with loop-based safeMax to prevent overflow - Extract magic numbers into named constants (chart heights, NEC limits, debounce/timing values, statistics threshold) - Strengthen ESLint with typescript-eslint, eqeqeq, no-var, prefer-const, no-shadow, consistent-return - Add vitest with 89 tests covering all pure helper functions and core utilities (sanitize, layout, history, format, entity-finder, graph-settings, chart-options, monitoring-status) - Add tsconfig.json, update rollup to use @rollup/plugin-typescript - Update CI workflow with typecheck, test, and TS-aware prettier glob - Update lefthook pre-commit hooks with typecheck step - Update i18n validation script for .ts files - Add DEVELOPER.md with complete project documentation --- .github/workflows/ci.yml | 4 +- DEVELOPER.md | 221 ++ dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- eslint.config.js | 24 +- lefthook.yml | 9 +- package-lock.json | 2054 +++++++++++++++-- package.json | 15 +- rollup.config.mjs | 7 +- scripts/validate-i18n.mjs | 11 +- src/card/card-discovery.js | 162 -- src/card/card-discovery.ts | 220 ++ src/card/{card-styles.js => card-styles.ts} | 2 +- src/card/span-panel-card.js | 621 ----- src/card/span-panel-card.ts | 267 +++ src/chart/chart-options.js | 124 - src/chart/chart-options.ts | 195 ++ src/chart/chart-update.js | 17 - src/chart/chart-update.ts | 34 + src/{constants.js => constants.ts} | 58 +- src/core/dashboard-controller.ts | 414 ++++ src/core/{dom-updater.js => dom-updater.ts} | 83 +- .../{graph-settings.js => graph-settings.ts} | 44 +- .../{grid-renderer.js => grid-renderer.ts} | 92 +- ...{header-renderer.js => header-renderer.ts} | 32 +- .../{history-loader.js => history-loader.ts} | 96 +- ...itoring-status.js => monitoring-status.ts} | 76 +- src/core/{side-panel.js => side-panel.ts} | 224 +- ...ice-renderer.js => sub-device-renderer.ts} | 92 +- ...rd-editor.js => span-panel-card-editor.ts} | 127 +- src/helpers/chart.js | 14 - src/helpers/chart.ts | 16 + src/helpers/entity-finder.js | 38 - src/helpers/entity-finder.ts | 35 + src/helpers/format.js | 20 - src/helpers/format.ts | 16 + src/helpers/history.js | 53 - src/helpers/history.ts | 65 + src/helpers/{layout.js => layout.ts} | 8 +- src/helpers/sanitize.js | 5 - src/helpers/sanitize.ts | 5 + src/{i18n.js => i18n.ts} | 10 +- src/{index.js => index.ts} | 15 +- src/panel/{index.js => index.ts} | 0 src/panel/{span-panel.js => span-panel.ts} | 125 +- src/panel/tab-dashboard.js | 452 ---- src/panel/tab-dashboard.ts | 110 + .../{tab-monitoring.js => tab-monitoring.ts} | 205 +- .../{tab-settings.js => tab-settings.ts} | 50 +- src/types.ts | 217 ++ tests/chart-options.test.ts | 95 + tests/entity-finder.test.ts | 71 + tests/format.test.ts | 56 + tests/graph-settings.test.ts | 67 + tests/history.test.ts | 125 + tests/layout.test.ts | 56 + tests/monitoring-status.test.ts | 105 + tests/sanitize.test.ts | 30 + tsconfig.json | 21 + 59 files changed, 5151 insertions(+), 2263 deletions(-) create mode 100644 DEVELOPER.md delete mode 100644 src/card/card-discovery.js create mode 100644 src/card/card-discovery.ts rename src/card/{card-styles.js => card-styles.ts} (99%) delete mode 100644 src/card/span-panel-card.js create mode 100644 src/card/span-panel-card.ts delete mode 100644 src/chart/chart-options.js create mode 100644 src/chart/chart-options.ts delete mode 100644 src/chart/chart-update.js create mode 100644 src/chart/chart-update.ts rename src/{constants.js => constants.ts} (59%) create mode 100644 src/core/dashboard-controller.ts rename src/core/{dom-updater.js => dom-updater.ts} (79%) rename src/core/{graph-settings.js => graph-settings.ts} (62%) rename src/core/{grid-renderer.js => grid-renderer.ts} (77%) rename src/core/{header-renderer.js => header-renderer.ts} (81%) rename src/core/{history-loader.js => history-loader.ts} (66%) rename src/core/{monitoring-status.js => monitoring-status.ts} (57%) rename src/core/{side-panel.js => side-panel.ts} (79%) rename src/core/{sub-device-renderer.js => sub-device-renderer.ts} (59%) rename src/editor/{span-panel-card-editor.js => span-panel-card-editor.ts} (73%) delete mode 100644 src/helpers/chart.js create mode 100644 src/helpers/chart.ts delete mode 100644 src/helpers/entity-finder.js create mode 100644 src/helpers/entity-finder.ts delete mode 100644 src/helpers/format.js create mode 100644 src/helpers/format.ts delete mode 100644 src/helpers/history.js create mode 100644 src/helpers/history.ts rename src/helpers/{layout.js => layout.ts} (58%) delete mode 100644 src/helpers/sanitize.js create mode 100644 src/helpers/sanitize.ts rename src/{i18n.js => i18n.ts} (99%) rename src/{index.js => index.ts} (80%) rename src/panel/{index.js => index.ts} (100%) rename src/panel/{span-panel.js => span-panel.ts} (66%) delete mode 100644 src/panel/tab-dashboard.js create mode 100644 src/panel/tab-dashboard.ts rename src/panel/{tab-monitoring.js => tab-monitoring.ts} (78%) rename src/panel/{tab-settings.js => tab-settings.ts} (79%) create mode 100644 src/types.ts create mode 100644 tests/chart-options.test.ts create mode 100644 tests/entity-finder.test.ts create mode 100644 tests/format.test.ts create mode 100644 tests/graph-settings.test.ts create mode 100644 tests/history.test.ts create mode 100644 tests/layout.test.ts create mode 100644 tests/monitoring-status.test.ts create mode 100644 tests/sanitize.test.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 286b2ec..e2bd873 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,9 @@ jobs: - run: npm ci - run: npx eslint src/ - - run: npx prettier --check "**/*.{js,mjs,json,md,yml,yaml}" + - run: npx prettier --check "**/*.{ts,js,mjs,json,md,yml,yaml}" + - run: npx tsc --noEmit + - run: npx vitest run - run: npx markdownlint-cli2 "**/*.md" build: diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 0000000..b6e766e --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,221 @@ +# Developer Guide + +## Prerequisites + +- Node.js 20+ +- npm + +## Setup + +```bash +npm install +``` + +This installs all dev dependencies and sets up lefthook pre-commit hooks. + +## Scripts + +| Command | Description | +| -------------------- | -------------------------------------------------- | +| `npm run build` | Type-check and produce minified production bundles | +| `npm run dev` | Watch mode with hot-reload (no minification) | +| `npm run typecheck` | Type-check only (no output) | +| `npm run lint` | Run ESLint on `src/` | +| `npm test` | Run test suite | +| `npm run test:watch` | Run tests in watch mode | + +## Project Structure + +```text +src/ + types.ts # Shared TypeScript interfaces + constants.ts # Global constants, metric definitions, named magic numbers + i18n.ts # Internationalization (en, es, fr, ja, pt) + index.ts # Card entry point — registers custom elements + + card/ + span-panel-card.ts # Main Lovelace card (extends HTMLElement) + card-discovery.ts # WebSocket topology discovery + entity fallback + card-styles.ts # Card CSS + + core/ + dashboard-controller.ts # Shared controller (used by card + panel dashboard) + grid-renderer.ts # Breaker grid HTML builder + header-renderer.ts # Panel header HTML builder + sub-device-renderer.ts # BESS/EVSE sub-device HTML builder + dom-updater.ts # Incremental DOM updates for live data + history-loader.ts # Loads history from HA recorder (raw + statistics) + monitoring-status.ts # Monitoring status cache + utilization helpers + graph-settings.ts # Graph horizon settings cache + effective horizon lookup + side-panel.ts # Sliding configuration panel (custom element) + + panel/ + index.ts # Integration panel entry point + span-panel.ts # Panel container with tab navigation + tab-dashboard.ts # Dashboard tab (delegates to DashboardController) + tab-monitoring.ts # Monitoring configuration tab + tab-settings.ts # Settings tab (graph horizons, integration link) + + editor/ + span-panel-card-editor.ts # Visual card editor for Lovelace UI + + chart/ + chart-options.ts # ECharts configuration builder + chart-update.ts # Chart DOM creation/update + + helpers/ + sanitize.ts # HTML escaping (XSS prevention) + format.ts # Power/unit formatting + layout.ts # Tab-to-grid position calculations + chart.ts # Chart metric selection + history.ts # History duration, sampling, deduplication + entity-finder.ts # Sub-device entity discovery by name/suffix + +tests/ # Unit tests (vitest) +scripts/ + validate-i18n.mjs # Validates translation key consistency across languages + fix-markdown.sh # Markdown formatting helper +dist/ + span-panel-card.js # Card bundle (IIFE, minified) + span-panel.js # Integration panel bundle (IIFE, minified) +``` + +## Architecture + +The project produces two independent bundles from two entry points: + +1. **`src/index.ts`** → `dist/span-panel-card.js` — the Lovelace custom card +2. **`src/panel/index.ts`** → `dist/span-panel.js` — the integration panel + +Both share the same core modules. The `DashboardController` in `src/core/dashboard-controller.ts` encapsulates all shared dashboard behavior (live sampling, +history loading, horizon maps, slide-to-confirm, toggle/gear clicks, resize observation) so the card and panel dashboard tab delegate to it rather than +duplicating logic. + +### Data Flow + +```text +Home Assistant + ├─ Device Registry → panel discovery + ├─ Entity Registry → entity fallback discovery + ├─ WebSocket API → span_panel/panel_topology + ├─ Recorder (history) → raw history + statistics + └─ hass.states → live entity state + │ + ▼ + DashboardController + ├─ recordSamples() → live power sampling (1s interval) + ├─ refreshRecorderData() → periodic recorder refresh (30s) + ├─ buildHorizonMaps() → per-circuit/sub-device graph horizons + └─ updateDOM() → incremental DOM updates + │ + ▼ + Renderers (grid, header, sub-device, chart) +``` + +### Key Types + +All shared types live in `src/types.ts`: + +- `HomeAssistant` — subset of the HA frontend type used by this project +- `PanelTopology` — circuits, sub-devices, panel entities +- `Circuit` — name, tabs, entities, breaker rating, shedding info +- `CardConfig` — user-facing card configuration +- `HistoryMap` — `Map` for power/current history +- `ChartMetricDef` — defines how a metric is formatted and displayed +- `GraphSettings` — per-circuit and global graph horizon overrides +- `MonitoringStatus` — utilization, alerts, thresholds + +## TypeScript + +The project uses strict TypeScript with these compiler options: + +- `strict: true` +- `noUncheckedIndexedAccess: true` — forces guarding Record/array index access +- `noUnusedLocals: true` +- `noUnusedParameters: true` +- Target: ES2020, bundled as IIFE by Rollup + +Type-checking runs before every build (`tsc --noEmit && rollup -c`) and in the pre-commit hook. + +## Build + +Rollup bundles each entry point into a single IIFE file: + +- **Production** (`npm run build`): TypeScript plugin compiles `.ts`, then Terser minifies +- **Development** (`npm run dev`): TypeScript plugin compiles `.ts`, no minification, watch mode + +The TypeScript plugin handles compilation; `tsc --noEmit` is used only for type-checking. No `.js` output is produced by `tsc` directly. + +## Linting + +ESLint uses the flat config format with `typescript-eslint`: + +- `eslint:recommended` + `tseslint.configs.recommended` +- `eqeqeq` (strict equality) +- `no-var`, `prefer-const` +- `@typescript-eslint/no-shadow` +- `consistent-return` +- Unused vars with `argsIgnorePattern: ^_` + +Prettier handles formatting (160 char width, 2-space indent, double quotes). + +## Testing + +Tests use [Vitest](https://vitest.dev/) and live in `tests/`. They cover all pure helper functions and core utilities: + +```bash +npm test # single run +npm run test:watch # watch mode +``` + +To add a test, create `tests/.test.ts` and import from `../src/.js` (TypeScript resolves `.js` imports to `.ts` files). + +## Pre-commit Hooks + +Lefthook runs these checks in parallel on staged files: + +| Hook | Glob | Command | +| ------------- | -------------------------------- | -------------------------------- | +| prettier | `*.{ts,js,mjs,json,md,yml,yaml}` | `prettier --check` | +| eslint | `*.{ts,js,mjs}` | `eslint` | +| typecheck | `src/**/*.ts` | `tsc --noEmit` | +| markdownlint | `*.md` | `markdownlint-cli2` | +| i18n-validate | `src/**/*.ts` | `node scripts/validate-i18n.mjs` | + +## CI + +GitHub Actions (`.github/workflows/ci.yml`) runs on push/PR to `main`: + +- **lint job**: ESLint, Prettier, TypeScript type-check, Vitest, markdownlint +- **build job**: Full production build (`npm run build`) + +## Internationalization + +Translations live in `src/i18n.ts` with 5 languages: `en`, `es`, `fr`, `ja`, `pt`. + +The `scripts/validate-i18n.mjs` script checks: + +1. Every `t("key")` call in source has a matching English key +2. Every English key exists in all other languages +3. No orphaned keys in non-English languages + +Run manually: `node scripts/validate-i18n.mjs` + +## Adding a New Translation Key + +1. Add the key to the `en` block in `src/i18n.ts` +2. Add translations for `es`, `fr`, `ja`, `pt` +3. Run `node scripts/validate-i18n.mjs` to verify + +## Distribution + +The SPAN Panel integration serves the card automatically from its static path. No HACS or manual installation is required. The integration registers both +`dist/span-panel-card.js` and `dist/span-panel.js` as Lovelace resources. + +To update the distributed files after changes: + +```bash +npm run build +``` + +Then commit the updated `dist/` files. diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 588c4f7..ead3729 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",h="sub_",p={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:p.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},_="#ff9800",m={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>m[e])}function v(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function b(e){const t=s[e];return t?t.ms:s[o].ms}function y(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function w(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function x(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function C(e){return p[e.chart_metric]||p[i]}function S(e,t){const n=function(e){return C(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class z{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}const E=p.power;function k(e){return E.unit(e)}function $(e){return(e<0?"-":"")+E.format(e)}function M(e){return(Math.abs(e)/1e3).toFixed(1)}function P(e){return Math.ceil(e/2)}function N(e){return e%2==0?1:0}function A(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return P(t)===P(n)?"row-span":N(t)===N(n)?"col-span":"row-span"}class L{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=o?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function D(e,t,i,o,s,a,l,d,h,p){const u=t.entities?.power,m=u?l.states[u]:null,v=m&&parseFloat(m.state)||0,b=t.device_type===c||v<0,y=t.entities?.switch,w=y?l.states[y]:null,x=w?"on"===w.state:(m?.attributes?.relay_state||t.relay_state)===r,S=t.breaker_rating_a,z=S?`${Math.round(S)}A`:"",E=f(t.name||n("grid.unknown")),M=C(d);let P;if("current"===M.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${M.format(i)}A`}else P=`${$(v)}${k(v)}`;const N=g[p||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=h&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(h),D=L?_:"#555",T=``;let R="";if(null!=h?.utilization_pct){const e=h.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(h);R=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(h);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${E}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(x?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${R}\n ${T}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const R={names:["power","battery power"],suffixes:["_power"]},H={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},O={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function F(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function j(e){return F(e,R)}function G(e){return F(e,H)}function q(e){return F(e,I)}function W(e){return F(e,O)}function V(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function B(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${h}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${h}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${h}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}async function U(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function X(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=y(i),c=function(e){return Math.max(500,Math.floor(e/5e3))}(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,x(t,r,c))}}}function J(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:j(i)};i.type===l&&(e.soc=G(i),e.soe=q(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${h}${n}_${i}`,devId:n})}return t}async function K(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=S(i,n);if(!t)continue;let s;s=o&&o.has(e)?b(o.get(e)):v(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of J(t)){let t;t=s&&s.has(o)?b(s.get(o)):v(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(U(e,n.entityIds,n.uuidByEntity,t,i)):r.push(X(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=p[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,h=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],_=u.length>0?Math.max(...u.map(e=>e[1])):0,m={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(m.min=n.fixedMin,m.max=n.fixedMax):_<1&&(m.min=0,m.max=1),s&&"current"===n.entityRole&&(m.min=0,m.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:m,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(n,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r||120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=v(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=M(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=M(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=M(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=M(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const h=C(o),p="current"===h.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,_=u?t.states[u]:null,m=_&&parseFloat(_.state)||0,f=d.device_type===c||m<0,v=d.entities?.switch,y=v?t.states[v]:null,w=y?"on"===y.state:(_?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${h.format(i)}A`}else x.innerHTML=`${$(m)}${k(m)}`;const C=i.querySelector(".toggle-pill");if(C){C.className="toggle-pill "+(w?"toggle-on":"toggle-off");const e=C.querySelector(".toggle-label");e&&(e.textContent=n(w?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!w),i.classList.toggle("circuit-producer",f),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const z=g[S]||g.unknown,E=i.querySelector(".shedding-icon");E&&(E.setAttribute("icon",z.icon),E.style.color=z.color,E.title=z.label());const M=i.querySelector(".shedding-icon-secondary");M&&(z.icon2?(M.setAttribute("icon",z.icon2),M.style.color=z.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".shedding-label");P&&(z.textLabel?(P.textContent=z.textLabel,P.style.color=z.color,P.style.display=""):P.style.display="none");const N=i.querySelector(".chart-container");if(N){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(N,t,e,a?.has(o)?b(a.get(o)):l,h,f,n,d.breaker_rating_a)}}}function Z(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}const ee=500,te=Object.keys(g).filter(e=>"unknown"!==e);class ne extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,h=c?.circuits??{},p=document.createElement("div");p.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),p.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=n("sidepanel.global_default"),g.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(m),p.appendChild(g),a.appendChild(p),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=h[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,ee,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,ee,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}i.appendChild(h),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of te){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}i.appendChild(h),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,l.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,_=r?.window_duration_m??15,m=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",_,1,180,"m",t)),p.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",m,1,180,"m",t)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=h.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,ee,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(o),p.max=String(s),p.value=String(i),p.dataset.role=`threshold-${t}`,c&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=a,h.appendChild(p),h.appendChild(u),c||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,ee,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(h),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",ne);class ie extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._powerHistory=new Map,this._historyLoaded=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._rendered=!1,this._handleToggleClick=this._onToggleClick.bind(this),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=this._onGearClick.bind(this),this._handleGraphSettingsChanged=this._onGraphSettingsChanged.bind(this),this._monitoringCache=new L,this._graphSettingsCache=new z,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._resizeObserver=null,this._lastCardWidth=0,this._resizeDebounce=null}connectedCallback(){this._updateInterval=setInterval(()=>{this._discovered&&this._hass&&this._updateData()},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._discovered||!this._hass||!this._topology)return;const e=new Map;for(const[t,n]of this._horizonMap)s[n]?.useRealtime||e.set(t,n);if(0===e.size)return;const t=new Map;try{await K(this._hass,this._topology,this._config,t,e);for(const n of e.keys()){const e=t.get(n);e?this._powerHistory.set(n,e):this._powerHistory.delete(n)}this._updateDOM()}catch{}},3e4),this._discovered&&this._hass&&this._rendered&&this._updateDOM(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._updateDOM()},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._powerHistory.clear(),this._horizonMap.clear(),this._subDeviceHorizonMap.clear(),this._monitoringCache.clear(),this._graphSettingsCache.clear()}get _durationMs(){return v(this._config)}get _configEntryId(){return this._panelDevice?.config_entries?.[0]||null}set hass(i){var o;if(this._hass=i,o=i?.language,e=t[o]?o:"en",this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&this._updateData()):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._render(),this._loadHistory(),this._monitoringCache.fetch(i,this._configEntryId).then(()=>{this._rendered&&this._updateDOM()})}));this.shadowRoot.innerHTML=`\n \n
\n ${n("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||Z(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,t){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=i.find(e=>e.id===t)||null;if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===t),c=i.filter(e=>e.via_device_id===t),l=new Set(c.map(e=>e.id)),d=o.filter(e=>l.has(e.device_id)),h={},p=s.name_by_user||s.name||"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n||!n.attributes||!n.attributes.tabs)continue;const i=n.attributes.tabs;if(!i||!i.startsWith("tabs ["))continue;const o=i.slice(6,-1);let s;if(s=o.includes(":")?o.split(":").map(Number):[Number(o)],!s.every(Number.isFinite))continue;const a=t.unique_id.split("_");let r=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(a[e])){r=a[e];break}if(!r)continue;let c=n.attributes.friendly_name||t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}p&&c.startsWith(p+" ")&&(c=c.slice(p.length+1));const l=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,"");h[r]={tabs:s,name:c,voltage:n.attributes.voltage||(2===s.length?240:120),device_type:n.attributes.device_type||"circuit",relay_state:n.attributes.relay_state||"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:{power:t.entity_id,switch:`switch.${l}_breaker`,breaker_rating:`sensor.${l}_breaker_rating`}}}let u="";if(s.identifiers)for(const e of s.identifiers)e[0]===a&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&n.attributes&&n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Z(h)),!g)throw new Error(n("card.panel_size_error"));const _={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model||"").toLowerCase().includes("battery")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("bess")),s=(t.model||"").toLowerCase().includes("drive")||(t.identifiers||[]).some(e=>(e[1]||"").toLowerCase().includes("evse")),a={};for(const t of n)a[t.entity_id]={domain:t.entity_id.split(".")[0],original_name:e.states[t.entity_id]?.attributes?.friendly_name||t.entity_id};_[t.id]={name:t.name_by_user||t.name||"",type:i?"bess":s?"evse":"unknown",entities:a}}return{topology:{serial:u,firmware:s.sw_version||"",panel_size:g,device_id:t,device_name:s.name_by_user||s.name||n("header.default_name"),circuits:h,sub_devices:_},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0;try{await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._horizonMap.set(t,i)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._subDeviceHorizonMap.set(t,i)}}catch{}try{await K(this._hass,this._topology,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap),this._updateDOM()}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_recordPowerHistory(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=S(n,this._config);if(!a)continue;const r=this._hass.states[a],c=r&&parseFloat(r.state)||0,l=b(i),d=y(l),h=e-l;w(this._powerHistory,t,c,e,h,d)}for(const{entityId:t,key:n,devId:i}of J(this._topology)){const a=this._subDeviceHorizonMap?.get(i)||o;if(!s[a]?.useRealtime)continue;const r=this._hass.states[t],c=r&&parseFloat(r.state)||0,l=b(a),d=y(l),h=e-l;w(this._powerHistory,n,c,e,h,d)}}_updateData(){this._recordPowerHistory(),this._updateDOM()}_updateDOM(){Y(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=v(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=j(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${k(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?b(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}(this.shadowRoot,this._hass,this._topology,this._config,this._powerHistory,this._subDeviceHorizonMap)}async _onUnitToggle(e){const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==(this._config.chart_metric||"power")&&(this._config={...this._config,chart_metric:n},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._updateDOM())}_bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_onToggleClick(e){const t=e.target.closest(".toggle-pill");if(!t)return;const n=this.shadowRoot.querySelector(".slide-confirm");if(!n||!n.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const i=t.closest("[data-uuid]");if(!i||!this._topology||!this._hass)return;const o=i.dataset.uuid,s=this._topology.circuits[o];if(!s)return;const a=s.entities?.switch;if(!a)return;const r=this._hass.states[a];if(!r)return void console.warn("SPAN Panel: switch entity not found:",a);const c="on"===r.state?"turn_off":"turn_on";this._hass.callService("switch",c,{},{entity_id:a}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async _onGearClick(e){const t=e.target.closest(".gear-icon");if(!t)return;const n=this.shadowRoot.querySelector("span-side-panel");if(!n)return;if(n.hass=this._hass,t.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass,this._configEntryId),void n.open({panelMode:!0,topology:this._topology,graphSettings:this._graphSettingsCache.settings});const i=t.dataset.uuid;if(i&&this._topology){const e=this._topology.circuits[i];if(e){const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const s=this._graphSettingsCache.settings,a=s?.global_horizon||o,r=s?.circuits?.[i]?{...s.circuits[i],globalHorizon:a}:{horizon:a,has_override:!1,globalHorizon:a};return void n.open({...e,uuid:i,monitoringInfo:t,graphHorizonInfo:r})}}const s=t.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const e=this._topology.sub_devices[s];await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const t=this._graphSettingsCache.settings,i=t?.global_horizon||o,a=t?.sub_devices?.[s]?{...t.sub_devices[s],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};n.open({subDeviceMode:!0,subDeviceId:s,name:e.name||s,deviceType:e.type||"",graphHorizonInfo:a})}}async _onGraphSettingsChanged(){this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const e=this._graphSettingsCache.settings;if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits)){const n=e.circuits?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._horizonMap.set(t,i)}if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=e.sub_devices?.[t],i=n?.has_override?n.horizon:e.global_horizon||o;this._subDeviceHorizonMap.set(t,i)}this._powerHistory.clear(),this._historyLoaded=!1,await this._loadHistory()}_invalidateCharts(){for(const e of this.shadowRoot.querySelectorAll(".chart-container")){const t=e.querySelector("ha-chart-base");t&&t.remove()}}_setupResizeObserver(){this._resizeObserver&&this._resizeObserver.disconnect();const e=this.shadowRoot.querySelector("ha-card");e&&(this._lastCardWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const t=e[0];if(!t)return;const n=t.contentRect.width;Math.abs(n-this._lastCardWidth)<5||(this._lastCardWidth=n,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{this._invalidateCharts(),this._updateDOM()},150))}),this._resizeObserver.observe(e))}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError||(this._topology?n("card.loading"):n("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${f(e)}\n
\n
\n `)}const t=this._topology,i=Math.ceil(this._panelSize/2),o=(this._durationMs,function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,h=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(t,this._config)),s=this._monitoringCache.status,a=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":A(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=P(Math.max(...n));0===N(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let h="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),p=a.get(n);if(h+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);h+=D(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),h+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(h+=T(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);h+=D(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(n)||(h+=T(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(p);h+=D(p.uuid,p.circuit,e,"3",p.layout,0,i,o,t,n)}h+=`
${n}
`}return h}(t,i,0,e,this._config,s),c=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,h="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=j(o),p=a?t.states[a]:null,u=p&&parseFloat(p.state)||0,g=o.type===l,_=o.type===d,m=g?G(o):null,v=g?q(o):null,b=g?W(o):null,y=V(o,t,i,new Set([a,m,v,b].filter(Boolean))),w=B(e,0,g,a,m,v);let x="";g?x="sub-device-bess":_&&(c++,c===r&&r%2==1&&(x="sub-device-full")),h+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${k(u)}`:""}\n \n
\n ${w}\n ${y}\n
\n `}return h}(t,e,this._config);this.shadowRoot.removeEventListener("click",this._handleToggleClick),this.shadowRoot.removeEventListener("click",this._handleUnitToggle),this.shadowRoot.removeEventListener("click",this._handleGearClick),this.shadowRoot.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),this.shadowRoot.innerHTML=`\n \n \n ${o}\n ${a}\n ${c?`
${c}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,this.shadowRoot.addEventListener("click",this._handleToggleClick),this.shadowRoot.addEventListener("click",this._handleUnitToggle),this.shadowRoot.addEventListener("click",this._handleGearClick),this.shadowRoot.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const h=this.shadowRoot.querySelector(".slide-confirm");if(h){this._bindSlideConfirm(h,this.shadowRoot.querySelector("ha-card"));const e=this.shadowRoot.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const p=this.shadowRoot.querySelector("span-side-panel");p&&(p.hass=e),this._rendered=!0,this._recordPowerHistory(),this._updateDOM(),this._setupResizeObserver()}}class oe extends HTMLElement{constructor(){super(),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers||[]).some(e=>e[0]===a)&&!e.via_device_id).map(e=>{const t=(e.identifiers||[]).find(e=>e[0]===a)?.[1]||"",i=e.name_by_user||e.name||n("editor.panel_label");return{device_id:e.id,label:`${i} (${t})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=t;const c=document.createElement("option");if(c.value="",c.textContent=n("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=t+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(this._config.history_days)||0,h=parseInt(this._config.history_hours)||0,p=parseInt(this._config.history_minutes)||0,u=l(d,"0","30",n("editor.days")),g=l(h,"0","23",n("editor.hours")),_=l(p,"0","59",n("editor.minutes")),m=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",m),g.input.addEventListener("change",m),_.input.addEventListener("change",m),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=t,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,t,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=n("editor.visible_sections"),s.style.cssText=t,o.appendChild(s);const a=[{key:"show_panel",label:n("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:n("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:n("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name||"").toLowerCase(),o=t.unique_id||"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities||{};for(const[,n]of Object.entries(e)){const e=this._entityContainers[n.type];if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name||i;const l=n.name||"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities||{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${a}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits||{}))for(const t of Object.keys(e.entities||{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric||i;e.innerHTML="";for(const[n,i]of Object.entries(p)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id||""),this._daysInput&&(this._daysInput.value=String(parseInt(this._config.history_days)||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(this._config.history_hours)||0)),this._minsInput&&(this._minsInput.value=String(parseInt(this._config.history_minutes)||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric||i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.get("span-panel-card")||customElements.define("span-panel-card",ie),customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",oe),window.customCards=window.customCards||[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.9.0 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en?.[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",h="sub_",p=500,u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},g={soc:{entityRole:"soc",label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},f={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},_="#ff9800",m={"&":"&","<":"<",">":">",'"':""","'":"'"};function v(e){return String(e).replace(/[&<>"']/g,e=>m[e]??e)}const b=u.power;function y(e){return b.unit(e)}function w(e){return(e<0?"-":"")+b.format(e)}function x(e){return(Math.abs(e)/1e3).toFixed(1)}function C(e){return Math.ceil(e/2)}function S(e){return e%2==0?1:0}function z(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return C(t)===C(n)?"row-span":S(t)===S(n)?"col-span":"row-span"}function E(e){const t=e.chart_metric??i;return u[t]??u[i]}function k(e,t){const n=function(e){return E(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class ${constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=o?.response??null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function M(e,t,i,o,s,a,l,d,h){const p=t.entities?.power,u=p?a.states[p]:null,g=u&&parseFloat(u.state)||0,m=t.device_type===c||g<0,b=t.entities?.switch,x=b?a.states[b]:null,C=x?"on"===x.state:(u?.attributes?.relay_state||t.relay_state)===r,S=t.breaker_rating_a,z=S?`${Math.round(S)}A`:"",k=v(t.name||n("grid.unknown")),$=E(l);let M;if("current"===$.entityRole){const e=t.entities?.current,n=e?a.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${$.format(i)}A`}else M=`${w(g)}${y(g)}`;const P=f[h||"unknown"]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};let N;N=P.icon2?`\n \n \n `:P.textLabel?`\n \n ${P.textLabel}\n `:``;const A=d&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(d),D=A?_:"#555",L=``;let T="";if(null!=d?.utilization_pct){const e=d.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(d);T=`${Math.round(e)}%`}const I=function(e){return!!e&&null!=e.over_threshold_since}(d);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${k}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(C?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${T}\n ${L}\n
\n
\n
\n `}function P(e,t){return`\n
\n \n
\n `}const N={names:["power","battery power"],suffixes:["_power"]},A={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},D={names:["state of energy"],suffixes:["_soe_kwh"]},L={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function T(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name??"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function I(e){return T(e,N)}function H(e){return T(e,A)}function R(e){return T(e,D)}function O(e){return T(e,L)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${v(c)}:\n ${v(d)}\n
\n `}return s}function G(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${h}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${h}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${h}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${v(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function j(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(String(e.history_days))||0)+(t&&parseInt(String(e.history_hours))||0))+(t?parseInt(String(e.history_minutes))||0:5))*1e3;return Math.max(n,6e4)}function q(e){const t=s[e];return t?t.ms:s[o].ms}function W(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function V(e){return Math.max(500,Math.floor(e/5e3))}function B(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);a.push({time:i,value:n});const r=a.findIndex(e=>e.time>=o);r>0?a.splice(0,r):-1===r&&(a.length=0),a.length>s&&a.splice(0,a.length-s)}function U(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}async function X(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function J(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=W(i),c=V(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,U(t,r,c))}}}function K(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:I(i)};i.type===l&&(e.soc=H(i),e.soe=R(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${h}${n}_${i}`,devId:n})}return t}async function Q(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=k(i,n);if(!t)continue;let s;s=o&&o.has(e)?q(o.get(e)):j(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of K(t)){let t;t=s&&s.has(o)?q(s.get(o)):j(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>2592e5?r.push(X(e,n.entityIds,n.uuidByEntity,t,i)):r.push(J(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function Y(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=u[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,h=n.unit(0),p=(e??[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:p,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],f=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):f<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||0===e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${h}
`}},animation:!1},series:g}}(n,o,s,a,c);let h=e.querySelector("ha-chart-base");h||(h=document.createElement("ha-chart-base"),h.style.display="block",h.style.width="100%",h.height=(r??120)+"px",e.innerHTML="",e.appendChild(h)),h.hass=t,h.options=l,h.data=d}function Z(e,t,i,o,s,a){if(!e||!i||!t)return;const l=j(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=x(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=x(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=x(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=x(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const h=E(o),p="current"===h.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,m=d.device_type===c||_<0,v=d.entities?.switch,b=v?t.states[v]:null,x=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===r,C=i.querySelector(".power-value");if(C)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;C.innerHTML=`${h.format(i)}A`}else C.innerHTML=`${w(_)}${y(_)}`;const S=i.querySelector(".toggle-pill");if(S){S.className="toggle-pill "+(x?"toggle-on":"toggle-off");const e=S.querySelector(".toggle-label");e&&(e.textContent=n(x?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!x),i.classList.toggle("circuit-producer",m),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const E=f[z]??f.unknown,k=i.querySelector(".shedding-icon");k&&(k.setAttribute("icon",E.icon),k.style.color=E.color,k.title=E.label());const $=i.querySelector(".shedding-icon-secondary");$&&(E.icon2?($.setAttribute("icon",E.icon2),$.style.color=E.color,$.style.display=""):$.style.display="none");const M=i.querySelector(".shedding-label");M&&(E.textLabel?(M.textContent=E.textLabel,M.style.color=E.color,M.style.display=""):M.style.display="none");const P=i.querySelector(".chart-container");if(P){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Y(P,t,e,a?.has(o)?q(a.get(o)):l,h,m,n,d.breaker_rating_a??void 0)}}}class ee{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response??null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}function te(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function ne(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class ie{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new $,this.graphSettingsCache=new ee,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._updateInterval=null,this._recorderRefreshInterval=null,this._resizeObserver=null,this._lastWidth=0,this._resizeDebounce=null}get hass(){return this._hass}set hass(e){this._hass=e}get topology(){return this._topology}get config(){return this._config}init(e,t,n,i){this._topology=e,this._config=t,this._hass=n,this._configEntryId=i}setConfig(e){this._config=e}buildHorizonMaps(e){if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits))this.horizonMap.set(t,te(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,ne(e,t))}async fetchAndBuildHorizonMaps(){try{await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings)}catch{}}async loadHistory(){await Q(this._hass,this._topology,this._config,this.powerHistory,this.horizonMap,this.subDeviceHorizonMap)}recordSamples(){if(!this._topology||!this._hass||!this._config)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this.horizonMap.get(t)??o;if(!s[i]?.useRealtime)continue;const a=k(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=q(i),d=W(l),h=V(l),p=e-l,u=this.powerHistory.get(t)??[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time${w(i)} ${y(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey;if(!n)continue;const r=o.get(n)||[];let c=g.power;n.endsWith("_soc")?c=g.soc:n.endsWith("_soe")&&(c=g.soe);const l=!!e.closest(".bess-chart-col");Y(e,t,r,s?.has(i)?q(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];if(o){const e=o.attributes.unit_of_measurement;i.textContent=`${o.state}${e?" "+e:""}`}}}}(e,this._hass,this._topology,this._config,this.powerHistory,this.subDeviceHorizonMap))}async onGraphSettingsChanged(e){if(this._hass){this.graphSettingsCache.invalidate(),await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings),this.powerHistory.clear();try{await this.loadHistory()}catch{}this.updateDOM(e)}}onToggleClick(e,t){const n=e.target,i=n?.closest(".toggle-pill");if(!i)return;const o=t.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const s=i.closest("[data-uuid]");if(!s||!this._topology||!this._hass)return;const a=s.dataset.uuid;if(!a)return;const r=this._topology.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return void console.warn("SPAN Panel: switch entity not found:",c);const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async onGearClick(e,t){const n=e.target,i=n?.closest(".gear-icon");if(!i)return;const s=t.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this.graphSettingsCache.fetch(this._hass,this._configEntryId),void s.open({panelMode:!0,topology:this._topology,graphSettings:this.graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&this._topology){const e=this._topology.circuits[a];if(e){await this.monitoringCache.fetch(this._hass,this._configEntryId);const t=e.entities?.power,n=t?this.monitoringCache.status?.circuits?.[t]??null:null;await this.graphSettingsCache.fetch(this._hass,this._configEntryId);const i=this.graphSettingsCache.settings,r=i?.global_horizon??o,c=i?.circuits?.[a],l=c?{...c,globalHorizon:r}:{horizon:r,has_override:!1,globalHorizon:r};return void s.open({...e,uuid:a,monitoringInfo:n,graphHorizonInfo:l})}}const r=i.dataset.subdevId;if(r&&this._topology?.sub_devices?.[r]){const e=this._topology.sub_devices[r];await this.graphSettingsCache.fetch(this._hass,this._configEntryId);const t=this.graphSettingsCache.settings,n=t?.global_horizon??o,i=t?.sub_devices?.[r],a=i?{...i,globalHorizon:n}:{horizon:n,has_override:!1,globalHorizon:n};s.open({subDeviceMode:!0,subDeviceId:r,name:e.name??r,deviceType:e.type??"",graphHorizonInfo:a})}}bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n||!i)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon")?.setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn??"",t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon")?.setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff??"",t&&t.classList.add("switches-disabled"))})}startIntervals(e,t){this._updateInterval=setInterval(()=>{this.recordSamples(),this.updateDOM(e),t&&t()},1e3),this._recorderRefreshInterval=setInterval(()=>{this.refreshRecorderData(e)},3e4)}stopIntervals(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this.cleanupResizeObserver()}setupResizeObserver(e,t){this.cleanupResizeObserver(),t&&(this._lastWidth=t.clientWidth,this._resizeObserver=new ResizeObserver(t=>{const n=t[0];if(!n)return;const i=n.contentRect.width;Math.abs(i-this._lastWidth)<5||(this._lastWidth=i,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}this.updateDOM(e)},150))}),this._resizeObserver.observe(t))}cleanupResizeObserver(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}reset(){this.powerHistory.clear(),this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),this.monitoringCache.clear(),this.graphSettingsCache.clear()}}function oe(e){let t=0;for(const n of Object.values(e))if(n)for(const e of n.tabs)e>t&&(t=e);return t>0?t+t%2:0}function se(e){return e?{id:e.id,name:e.name,name_by_user:e.name_by_user,config_entries:e.config_entries,identifiers:e.identifiers,via_device_id:e.via_device_id,sw_version:e.sw_version,model:e.model}:null}const ae=Object.keys(f).filter(e=>"unknown"!==e);class re extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;if(!t)return;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,h=c?.circuits??{},u=document.createElement("div");u.className="section";const g=document.createElement("div");g.className="section-label",g.textContent=n("sidepanel.graph_horizon"),u.appendChild(g);const f=document.createElement("div");f.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=n("sidepanel.global_default"),f.appendChild(_);const m=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===d&&(t.selected=!0),m.appendChild(t)}if(m.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:m.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),f.appendChild(m),u.appendChild(f),a.appendChild(u),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=h[t]||{horizon:d,has_override:!1},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,p,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const v=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=v[t]||{horizon:d,has_override:!1},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,p,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${v(String(t.breaker_rating_a))}A · ${v(String(t.voltage))}V · Tabs [${v(String(t.tabs))}]`,i=this._createHeader(v(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(v(t.name),v(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}i.appendChild(h),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),o=v(e),s=v(t);i.innerHTML=`
${o}
`+(s?`
${s}
`:"");const a=document.createElement("button");return a.className="close-btn",a.innerHTML="✕",a.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(a),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of ae){const t=f[e];if(!t)continue;const n=document.createElement("option");n.value=e,n.textContent=t.label(),e===c&&(n.selected=!0),a.appendChild(n)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))p.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of p){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(o)}i.appendChild(h),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,l.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,f=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",f,1,180,"m",t)),p.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=h.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,p,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),i=e.querySelector('[data-role="threshold-spike"]'),s=e.querySelector('[data-role="threshold-window-m"]'),a=e.querySelector('[data-role="threshold-cooldown-m"]'),r=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:r,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:s?Number(s.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${t}`,c&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=a,h.appendChild(u),h.appendChild(g),c||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,p,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),i=e.querySelector('[data-role="threshold-spike"]'),o=e.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:o?Number(o.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(h),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(!e.subDeviceMode){if(e.entities?.switch){const t=this.shadowRoot?.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot?.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._hass?this._hass.callWS({type:"call_service",domain:a,service:e,service_data:t,return_response:!0}):Promise.resolve()}_showError(e){const t=this.shadowRoot?.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}customElements.get("span-side-panel")||customElements.define("span-side-panel",re);class ce extends HTMLElement{constructor(){super(),this._hass=null,this._config={},this._discovered=!1,this._discovering=!1,this._discoveryError=null,this._topology=null,this._panelDevice=null,this._panelSize=0,this._historyLoaded=!1,this._rendered=!1,this._ctrl=new ie,this._onVisibilityChange=null,this.attachShadow({mode:"open"}),this._handleToggleClick=e=>this._ctrl.onToggleClick(e,this.shadowRoot),this._handleUnitToggle=this._onUnitToggle.bind(this),this._handleGearClick=e=>this._ctrl.onGearClick(e,this.shadowRoot),this._handleGraphSettingsChanged=()=>this._ctrl.onGraphSettingsChanged(this.shadowRoot)}connectedCallback(){this._ctrl.startIntervals(this.shadowRoot),this._discovered&&this._hass&&this._rendered&&this._ctrl.updateDOM(this.shadowRoot),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._ctrl.updateDOM(this.shadowRoot)},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._ctrl.stopIntervals(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null)}setConfig(e){this._config=e,this._discovered=!1,this._rendered=!1,this._historyLoaded=!1,this._discoveryError=null,this._ctrl.reset(),this._ctrl.setConfig(e)}get _configEntryId(){return this._panelDevice?.config_entries?.[0]??null}set hass(i){var o;if(this._hass=i,this._ctrl.hass=i,o=i?.language,e=o&&t[o]?o:"en",this._config.device_id)return this._discovered||this._discovering?void(this._discovered&&(this._ctrl.recordSamples(),this._ctrl.updateDOM(this.shadowRoot))):(this._discovering=!0,void this._discoverTopology().then(()=>{this._discovered=!0,this._discovering=!1,this._ctrl.init(this._topology,this._config,this._hass,this._configEntryId),this._render(),this._loadHistory(),this._ctrl.monitoringCache.fetch(i,this._configEntryId).then(()=>{this._rendered&&this._ctrl.updateDOM(this.shadowRoot)})}));this.shadowRoot.innerHTML=`\n \n
\n ${n("card.no_device")}\n
\n
\n `}getCardSize(){return Math.ceil(this._panelSize/2)+3}static getConfigElement(){return document.createElement("span-panel-card-editor")}static getStubConfig(){return{device_id:"",history_days:0,history_hours:0,history_minutes:5,chart_metric:i,show_panel:!0,show_battery:!0,show_evse:!0}}async _discoverTopology(){if(this._hass)try{const e=await async function(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size??oe(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:se((await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)),panelSize:o}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(e,t){const[i,o]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),s=se(i.find(e=>e.id===t));if(!s)return{topology:null,panelDevice:null,panelSize:0};const r=o.filter(e=>e.device_id===t),c=i.filter(e=>e.via_device_id===t),l=new Set(c.map(e=>e.id)),d=o.filter(e=>void 0!==e.device_id&&l.has(e.device_id)),h={},p=s.name_by_user??s.name??"";for(const t of[...r,...d]){const n=e.states[t.entity_id];if(!n)continue;const i=n.attributes,o=i.tabs;if("string"!=typeof o||!o.startsWith("tabs ["))continue;const s=o.slice(6,-1);let a;if(a=s.includes(":")?s.split(":").map(Number):[Number(s)],!a.every(Number.isFinite))continue;const r=t.unique_id.split("_");let c=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(t)){c=t;break}}if(!c)continue;let l=("string"==typeof i.friendly_name?i.friendly_name:void 0)??t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(l.endsWith(e)){l=l.slice(0,-e.length);break}p&&l.startsWith(p+" ")&&(l=l.slice(p.length+1));const d=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,""),u="number"==typeof i.voltage?i.voltage:2===a.length?240:120,g={power:t.entity_id,switch:`switch.${d}_breaker`,breaker_rating:`sensor.${d}_breaker_rating`};h[c]={tabs:a,name:l,voltage:u,device_type:"string"==typeof i.device_type?i.device_type:"circuit",relay_state:"string"==typeof i.relay_state?i.relay_state:"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:g}}let u="";if(s.identifiers)for(const e of s.identifiers)e[0]===a&&(u=e[1]);let g=0;for(const t of r){const n=e.states[t.entity_id];if(n&&"number"==typeof n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=oe(h)),!g)throw new Error(n("card.panel_size_error"));const f={};for(const t of c){const n=o.filter(e=>e.device_id===t.id),i=(t.model??"").toLowerCase(),s=i.includes("battery")||(t.identifiers??[]).some(e=>e[1].toLowerCase().includes("bess")),a=i.includes("drive")||(t.identifiers??[]).some(e=>e[1].toLowerCase().includes("evse")),r={};for(const t of n){const n=t.entity_id.split(".")[0],i=e.states[t.entity_id],o=i?.attributes?.friendly_name;r[t.entity_id]={domain:n??"",original_name:"string"==typeof o?o:t.entity_id}}f[t.id]={name:t.name_by_user??t.name??"",type:s?"bess":a?"evse":"unknown",entities:r}}return{topology:{serial:u,firmware:s.sw_version??"",panel_size:g,device_id:t,device_name:s.name_by_user??s.name??n("header.default_name"),circuits:h,sub_devices:f},panelDevice:s,panelSize:g}}(this._hass,this._config.device_id);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._discoveryError=e.message}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this._hass){this._historyLoaded=!0,await this._ctrl.fetchAndBuildHorizonMaps();try{await this._ctrl.loadHistory(),this._ctrl.updateDOM(this.shadowRoot)}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}async _onUnitToggle(e){const t=e.target,n=t?.closest(".unit-btn");if(!n)return;const i=n.dataset.unit;i&&i!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:i},this._ctrl.setConfig(this._config),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._ctrl.powerHistory.clear(),this._historyLoaded=!1,this._rendered=!1,this._render(),await this._loadHistory(),this._ctrl.updateDOM(this.shadowRoot))}_render(){const e=this._hass;if(!e||!this._topology||!this._panelSize){const e=this._discoveryError??(this._topology?n("card.loading"):n("card.device_not_found"));return void(this.shadowRoot.innerHTML=`\n \n
\n ${v(e)}\n
\n
\n `)}const t=this._topology,i=Math.ceil(this._panelSize/2),o=function(e,t){const i=v(e.device_name||n("header.default_name")),o=v(e.serial||""),s=v(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,h=!!e.panel_entities?.pv_power,p=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(f).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(t,this._config),s=this._ctrl.monitoringCache.status,a=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),i=Object.values(e.mains??{}),o=[...t,...i],s=o.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,o){const s=new Map,a=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":z(e)??"single";s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)a.add(t)}const r=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=C(Math.max(...n));0===S(e)?r.add(i):c.add(i)}function l(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=o?(s=o,a=t??"",s?.circuits?s.circuits[a]??null:null):null;var s,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:r}}let d="";for(let e=1;e<=t;e++){const t=2*e-1,o=2*e,h=s.get(t),p=s.get(o);if(d+=`
${t}
`,h&&"row-span"===h.layout){const{monInfo:t,sheddingPriority:s}=l(h);d+=M(h.uuid,h.circuit,e,"2 / 4","row-span",n,i,t,s),d+=`
${o}
`;continue}if(!r.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)a.has(t)||(d+=P(e,"2"));else{const{monInfo:t,sheddingPriority:o}=l(h);d+=M(h.uuid,h.circuit,e,"2",h.layout,n,i,t,o)}if(!c.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)a.has(o)||(d+=P(e,"3"));else{const{monInfo:t,sheddingPriority:o}=l(p);d+=M(p.uuid,p.circuit,e,"3",p.layout,n,i,t,o)}d+=`
${o}
`}return d}(t,i,e,this._config,s),c=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,h="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=I(o),p=a?t.states[a]:void 0,u=p&&parseFloat(p.state)||0,g=o.type===l,f=o.type===d,_=g?H(o):null,m=g?R(o):null,b=g?O(o):null,x=F(o,t,i,new Set([a,_,m,b].filter(e=>null!==e))),C=G(e,0,g,a,_,m);let S="";g?S="sub-device-bess":f&&(c++,c===r&&r%2==1&&(S="sub-device-full")),h+=`\n
\n
\n ${v(s)}\n ${v(o.name||"")}\n ${a?`${w(u)} ${y(u)}`:""}\n \n
\n ${C}\n ${x}\n
\n `}return h}(t,e,this._config),h=this.shadowRoot;h.removeEventListener("click",this._handleToggleClick),h.removeEventListener("click",this._handleUnitToggle),h.removeEventListener("click",this._handleGearClick),h.removeEventListener("graph-settings-changed",this._handleGraphSettingsChanged),h.innerHTML=`\n \n \n ${o}\n ${a}\n ${c?`
${c}
`:""}\n ${!1!==this._config.show_panel?`\n
\n ${r}\n
\n `:""}\n
\n \n `,h.addEventListener("click",this._handleToggleClick),h.addEventListener("click",this._handleUnitToggle),h.addEventListener("click",this._handleGearClick),h.addEventListener("graph-settings-changed",this._handleGraphSettingsChanged);const p=h.querySelector(".slide-confirm");if(p){this._ctrl.bindSlideConfirm(p,h.querySelector("ha-card"));const e=h.querySelector("ha-card");e&&e.classList.add("switches-disabled")}const u=h.querySelector("span-side-panel");u&&(u.hass=e),this._rendered=!0,this._ctrl.recordSamples(),this._ctrl.updateDOM(h),this._ctrl.setupResizeObserver(h,h.querySelector("ha-card"))}}class le extends HTMLElement{constructor(){super(...arguments),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1,this._panelSelect=null,this._daysInput=null,this._hoursInput=null,this._minsInput=null,this._metricSelect=null,this._checkboxes={},this._entityContainers={}}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers??[]).some(e=>e[0]===a)&&!e.via_device_id).map(e=>{const t=(e.identifiers??[]).find(e=>e[0]===a)?.[1]??"",i=e.name_by_user??e.name??n("editor.panel_label");return{device_id:e.id,label:`${i} (${t})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.panel_label"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=t;const c=document.createElement("option");if(c.value="",c.textContent=n("editor.select_panel"),r.appendChild(c),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,device_id:r.value},this._fireConfigChanged(),this._discoverAvailableRoles(r.value)}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._panelSelect=r}_buildTimeWindow(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.chart_window"),a.style.cssText=i;const r=document.createElement("div");r.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const c=t+"width: 70px; cursor: text;",l=(e,t,n,i)=>{const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 6px;";const s=document.createElement("input");s.type="number",s.min=t,s.max=n,s.value=String(e),s.style.cssText=c;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",o.appendChild(s),o.appendChild(a),{wrap:o,input:s}},d=parseInt(String(this._config.history_days))||0,h=parseInt(String(this._config.history_hours))||0,p=parseInt(String(this._config.history_minutes))||0,u=l(d,"0","30",n("editor.days")),g=l(h,"0","23",n("editor.hours")),f=l(p,"0","59",n("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(f.wrap),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,t,i,o){const s=document.createElement("div");s.style.cssText=o;const a=document.createElement("label");a.textContent=n("editor.chart_metric"),a.style.cssText=i;const r=document.createElement("select");r.style.cssText=t,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),s.appendChild(a),s.appendChild(r),e.appendChild(s),this._metricSelect=r}_buildSectionCheckboxes(e,t,i){const o=document.createElement("div");o.style.cssText=i;const s=document.createElement("label");s.textContent=n("editor.visible_sections"),s.style.cssText=t,o.appendChild(s);const a=[{key:"show_panel",label:n("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:n("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:n("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of a){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),o.appendChild(t),this._checkboxes[e.key]=n;let s=null;e.subDeviceType&&(s=document.createElement("div"),s.style.cssText="padding-left: 26px;",s.style.display=n.checked?"block":"none",o.appendChild(s),this._entityContainers[e.subDeviceType]=s),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},s&&(s.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(o)}_isChartEntity(e,t,n){const i=(t.original_name??"").toLowerCase(),o=t.unique_id??"";if("power"===i||"battery power"===i||o.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||o.endsWith("_battery_level")||o.endsWith("_battery_percentage"))return!0;if("state of energy"===i||o.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||o.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities??{};for(const[,n]of Object.entries(e)){const e=n.type?this._entityContainers[n.type]:void 0;if(e&&(e.innerHTML="",n.entities))for(const[i,o]of Object.entries(n.entities)){if("sensor"===o.domain&&this._isChartEntity(i,o,n.type??""))continue;const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===t[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const r=document.createElement("span");let c=o.original_name??i;const l=n.name??"";c.startsWith(l+" ")&&(c=c.slice(l.length+1)),r.textContent=c,r.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",s.appendChild(a),s.appendChild(r),e.appendChild(s),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities??{}};a.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${a}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits??{}))for(const t of Object.keys(e.entities??{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric??i;e.innerHTML="";for(const[n,i]of Object.entries(u)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const o=document.createElement("option");o.value=n,o.textContent=i.label(),n===t&&(o.selected=!0),e.appendChild(o)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id??""),this._daysInput&&(this._daysInput.value=String(parseInt(String(this._config.history_days))||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(String(this._config.history_hours))||0)),this._minsInput&&(this._minsInput.value=String(parseInt(String(this._config.history_minutes))||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric??i),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e]}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}customElements.get("span-panel-card")||customElements.define("span-panel-card",ce),customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",le),window.customCards=window.customCards??[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.9.0 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/dist/span-panel.js b/dist/span-panel.js index fe7ce38..e4fec99 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1 +1 @@ -!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},u={soc:{label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:h.power},g={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800",_={"&":"&","<":"<",">":">",'"':""","'":"'"};function f(e){return String(e).replace(/[&<>"']/g,e=>_[e])}const b=500,v=Object.keys(g).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const m=document.createElement("span");m.className="field-label",m.textContent=n("sidepanel.global_default"),g.appendChild(m);const _=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===d&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:_.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),g.appendChild(_),h.appendChild(g),a.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,b,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const f=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=f[t]||{},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,b,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${f(String(t.breaker_rating_a))}A · ${f(String(t.voltage))}V · Tabs [${f(String(t.tabs))}]`,i=this._createHeader(f(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(f(t.name),f(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div");i.innerHTML=`
${e}
`+(t?`
${t}
`:"");const o=document.createElement("button");return o.className="close-btn",o.innerHTML="✕",o.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(o),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of v){const t=document.createElement("option");t.value=e,t.textContent=g[e].label(),e===c&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]'),s=this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'),a=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:a,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0,cooldown_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(o),h.max=String(s),h.value=String(i),h.dataset.role=`threshold-${t}`,c&&(h.disabled=!0);const u=document.createElement("span");return u.textContent=a,p.appendChild(h),p.appendChild(u),c||h.addEventListener("input",()=>{this._debounce(`threshold-${t}`,b,()=>{const e=this.shadowRoot.querySelector('[data-role="threshold-continuous"]'),t=this.shadowRoot.querySelector('[data-role="threshold-spike"]'),i=this.shadowRoot.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:t?Number(t.value):void 0,window_duration_m:i?Number(i.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(e.entities?.switch){const t=this.shadowRoot.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._callService(a,e,t)}_showError(e){const t=this.shadowRoot.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size||function(e){let t=0;for(const n of Object.values(e||{}))for(const e of n.tabs||[])e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));return{topology:i,panelDevice:(await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t)||null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=h.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function C(e){return Math.ceil(e/2)}function k(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return C(t)===C(n)?"row-span":k(t)===k(n)?"col-span":"row-span"}function P(e){return h[e.chart_metric]||h[i]}function M(e,t){const n=function(e){return P(e).entityRole}(t);return e.entities?.[n]||e.entities?.power||null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=o?.response||null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,s,a,l,d,p,h){const u=t.entities?.power,_=u?l.states[u]:null,b=_&&parseFloat(_.state)||0,v=t.device_type===c||b<0,y=t.entities?.switch,x=y?l.states[y]:null,w=x?"on"===x.state:(_?.attributes?.relay_state||t.relay_state)===r,z=t.breaker_rating_a,C=z?`${Math.round(z)}A`:"",k=f(t.name||n("grid.unknown")),E=P(d);let M;if("current"===E.entityRole){const e=t.entities?.current,n=e?l.states[e]:null,i=n&&parseFloat(n.state)||0;M=`${E.format(i)}A`}else M=`${$(b)}${S(b)}`;const N=g[h||"unknown"]||g.unknown;let A;A=N.icon2?`\n \n \n `:N.textLabel?`\n \n ${N.textLabel}\n `:``;const L=p&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(p),T=L?m:"#555",D=``;let I="";if(null!=p?.utilization_pct){const e=p.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(p);I=`${Math.round(e)}%`}const H=function(e){return!!e&&null!=e.over_threshold_since}(p);return`\n
\n
\n
\n ${C?`${C}`:""}\n ${k}\n
\n
\n \n ${M}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${A}\n ${I}\n ${D}\n
\n
\n
\n `}function L(e,t){return`\n
\n \n
\n `}const T={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function q(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name||"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function R(e){return q(e,T)}function j(e){return q(e,D)}function G(e){return q(e,I)}function O(e){return q(e,H)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${f(c)}:\n ${f(d)}\n
\n `}return s}function W(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${f(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(e.history_days)||0)+(t&&parseInt(e.history_hours)||0))+(t?parseInt(e.history_minutes)||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=s[e];return t?t.ms:s[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);for(a.push({time:i,value:n});a.length>0&&a[0].times&&a.splice(0,a.length-s)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}function Q(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=h[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),u=(e||[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:u,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],m=u.length>0?Math.max(...u.map(e=>e[1])):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||!e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r||120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function Y(e,t,i,o,s,a){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=P(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,m=u?t.states[u]:null,_=m&&parseFloat(m.state)||0,f=d.device_type===c||_<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(m?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const C=g[z]||g.unknown,k=i.querySelector(".shedding-icon");k&&(k.setAttribute("icon",C.icon),k.style.color=C.color,k.title=C.label());const E=i.querySelector(".shedding-icon-secondary");E&&(C.icon2?(E.setAttribute("icon",C.icon2),E.style.color=C.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(C.textLabel?(P.textContent=C.textLabel,P.style.color=C.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;Q(M,t,e,a?.has(o)?V(a.get(o)):l,p,f,n,d.breaker_rating_a)}}}function Z(e,t,n,i,o,s){if(!n.sub_devices)return;const a=B(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=R(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey,r=o.get(n)||[];let c=u.power;n.endsWith("_soc")?c=u.soc:n.endsWith("_soe")&&(c=u.soe);const l=!!e.closest(".bess-chart-col");Q(e,t,r,s?.has(i)?V(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];o&&(i.textContent=`${o.state}${o.attributes.unit_of_measurement?" "+o.attributes.unit_of_measurement:""}`)}}}async function ee(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function te(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,K(t,r,c))}}}function ne(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:R(i)};i.type===l&&(e.soc=j(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ie(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=M(i,n);if(!t)continue;let s;s=o&&o.has(e)?V(o.get(e)):B(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of ne(t)){let t;t=s&&s.has(o)?V(s.get(o)):B(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>72e5?r.push(ee(e,n.entityIds,n.uuidByEntity,t,i)):r.push(te(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}class oe{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response||null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}class se{constructor(){this._topology=null,this._panelSize=0,this._powerHistory=new Map,this._monitoringCache=new N,this._graphSettingsCache=new oe,this._updateInterval=null,this._recorderRefreshInterval=null,this._horizonMap=new Map,this._subDeviceHorizonMap=new Map,this._hass=null,this._config=null,this._resizeObserver=null,this._lastContainerWidth=0,this._resizeDebounce=null,this._container=null}async render(e,t,i,a,r){this.stop(),this._hass=t,this._powerHistory.clear(),this._config=a,this._container=e,this._configEntryId=r||null;try{const e=await x(t,i);this._topology=e.topology,this._panelSize=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}await this._monitoringCache.fetch(t,this._configEntryId),await this._graphSettingsCache.fetch(t,this._configEntryId);const c=this._topology;this._horizonMap=new Map;const p=this._graphSettingsCache.settings;if(c?.circuits)for(const e of Object.keys(c.circuits)){const t=p?.circuits?.[e],n=t?.has_override?t.horizon:p?.global_horizon||o;this._horizonMap.set(e,n)}if(c?.sub_devices)for(const e of Object.keys(c.sub_devices)){const t=p?.sub_devices?.[e],n=t?.has_override?t.horizon:p?.global_horizon||o;this._subDeviceHorizonMap.set(e,n)}const h=Math.ceil(this._panelSize/2),u=(B(a),this._monitoringCache.status),m=function(e,t){const i=f(e.device_name||n("header.default_name")),o=f(e.serial||""),s=f(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(g).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(c,a),_=function(e){if(!e)return"";const t=Object.values(e.circuits||{}),i=Object.values(e.mains||{}),o=[...t,...i],s=o.filter(e=>e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(u),b=function(e,t,n,i,o,s){const a=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e);a.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)r.add(t)}const c=new Set,l=new Set;for(const[e,t]of a)if("col-span"===t.layout){const n=t.circuit.tabs,i=C(Math.max(...n));0===k(e)?c.add(i):l.add(i)}function d(e){const t=e.circuit.entities?.current||e.circuit.entities?.power,n=s?(o=s,a=t,o?.circuits&&o.circuits[a]||null):null;var o,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&i.states[t]?i.states[t].state:"unknown"}return{monInfo:n,sheddingPriority:r}}let p="";for(let e=1;e<=t;e++){const t=2*e-1,n=2*e,s=a.get(t),h=a.get(n);if(p+=`
${t}
`,s&&"row-span"===s.layout){const{monInfo:t,sheddingPriority:a}=d(s);p+=A(s.uuid,s.circuit,e,"2 / 4","row-span",0,i,o,t,a),p+=`
${n}
`;continue}if(!c.has(e))if(!s||"col-span"!==s.layout&&"single"!==s.layout)r.has(t)||(p+=L(e,"2"));else{const{monInfo:t,sheddingPriority:n}=d(s);p+=A(s.uuid,s.circuit,e,"2",s.layout,0,i,o,t,n)}if(!l.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(n)||(p+=L(e,"3"));else{const{monInfo:t,sheddingPriority:n}=d(h);p+=A(h.uuid,h.circuit,e,"3",h.layout,0,i,o,t,n)}p+=`
${n}
`}return p}(c,h,0,t,a,u),v=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=R(o),h=a?t.states[a]:null,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?j(o):null,b=g?G(o):null,v=g?O(o):null,y=F(o,t,i,new Set([a,_,b,v].filter(Boolean))),x=W(e,0,g,a,_,b);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${f(s)}\n ${f(o.name||"")}\n ${a?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(c,t,a);e.innerHTML=`\n \n ${m}\n ${_}\n ${v?`
${v}
`:""}\n ${!1!==a.show_panel?`\n
\n ${b}\n
\n `:""}\n \n `,this._bindGearClicks(e,c),this._bindToggleClicks(e,c),this._onSidePanelClosed=()=>{this._monitoringCache.invalidate(),this._graphSettingsCache.invalidate()},this._onGraphSettingsChanged=async()=>{this._graphSettingsCache.invalidate(),await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const t=this._graphSettingsCache.settings;if(c?.circuits)for(const e of Object.keys(c.circuits)){const n=t?.circuits?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._horizonMap.set(e,i)}if(c?.sub_devices)for(const e of Object.keys(c.sub_devices)){const n=t?.sub_devices?.[e],i=n?.has_override?n.horizon:t?.global_horizon||o;this._subDeviceHorizonMap.set(e,i)}this._powerHistory.clear();try{await ie(this._hass,c,this._config,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,this._hass,c,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,c,this._config,this._powerHistory,this._subDeviceHorizonMap)},e.addEventListener("side-panel-closed",this._onSidePanelClosed),e.addEventListener("graph-settings-changed",this._onGraphSettingsChanged);try{await ie(t,c,a,this._powerHistory,this._horizonMap,this._subDeviceHorizonMap)}catch{}Y(e,t,c,a,this._powerHistory,this._horizonMap),Z(e,t,c,a,this._powerHistory,this._subDeviceHorizonMap);const y=e.querySelector(".slide-confirm");y&&(this._bindSlideConfirm(y,e),e.classList.add("switches-disabled")),this._setupResizeObserver(e,c,a),this._updateInterval=setInterval(()=>{this._recordSamples(),Y(e,this._hass,c,this._config,this._powerHistory,this._horizonMap),Z(e,this._hass,c,this._config,this._powerHistory,this._subDeviceHorizonMap)},1e3),this._recorderRefreshInterval=setInterval(async()=>{if(!this._topology||!this._hass)return;const t=new Map;for(const[e,n]of this._horizonMap)s[n]?.useRealtime||t.set(e,n);if(0===t.size)return;const n=new Map;try{await ie(this._hass,this._topology,this._config,n,t,this._subDeviceHorizonMap);for(const e of t.keys()){const t=n.get(e);t?this._powerHistory.set(e,t):this._powerHistory.delete(e)}Y(e,this._hass,c,this._config,this._powerHistory,this._horizonMap)}catch{}},3e4)}_recordSamples(){if(!this._topology||!this._hass)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this._horizonMap?.get(t)||o;if(!s[i]?.useRealtime)continue;const a=M(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this._powerHistory.get(t)||[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon").setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn,t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon").setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff,t&&t.classList.add("switches-disabled"))})}_bindToggleClicks(e,t){e.addEventListener("click",n=>{const i=n.target.closest(".toggle-pill");if(!i)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;n.stopPropagation(),n.preventDefault();const s=i.closest("[data-uuid]");if(!s||!t||!this._hass)return;const a=s.dataset.uuid,r=t.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return;const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c})})}_bindGearClicks(e,t){e.addEventListener("click",async n=>{const i=n.target.closest(".gear-icon");if(!i)return;const s=e.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this._graphSettingsCache.fetch(this._hass,this._configEntryId),void s.open({panelMode:!0,topology:t,graphSettings:this._graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&t){const e=t.circuits[a];if(e){await this._monitoringCache.fetch(this._hass);const t=this._monitoringCache?.status?.circuits?.[e.entities?.power]||null;await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,r=n?.circuits?.[a]?{...n.circuits[a],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({...e,uuid:a,monitoringInfo:t,graphHorizonInfo:r})}}const r=i.dataset.subdevId;if(r&&t?.sub_devices?.[r]){const e=t.sub_devices[r];await this._graphSettingsCache.fetch(this._hass,this._configEntryId);const n=this._graphSettingsCache.settings,i=n?.global_horizon||o,a=n?.sub_devices?.[r]?{...n.sub_devices[r],globalHorizon:i}:{horizon:i,has_override:!1,globalHorizon:i};return void s.open({subDeviceMode:!0,subDeviceId:r,name:e.name||r,deviceType:e.type||"",graphHorizonInfo:a})}})}_setupResizeObserver(e,t,n){this._resizeObserver&&this._resizeObserver.disconnect(),this._lastContainerWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(i=>{const o=i[0];if(!o)return;const s=o.contentRect.width;Math.abs(s-this._lastContainerWidth)<5||(this._lastContainerWidth=s,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}Y(e,this._hass,t,n,this._powerHistory,this._horizonMap),Z(e,this._hass,t,n,this._powerHistory,this._subDeviceHorizonMap)},150))}),this._resizeObserver.observe(e)}stop(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null),this._container&&this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._container&&this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null)}}const ae="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",re="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",ce="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",le="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",de="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function pe(e,t,n,i,o){return`\n ${i}\n `}class he{constructor(){this._debounceTimer=null,this._configEntryId=null}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const s=o?.global_settings||{},r=!0===o?.enabled,c=o?.circuits||{},l=o?.mains||{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers||[];for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=s.notify_targets||"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=s.notification_title_template||"SPAN: {name} {alert_type}",m=s.notification_message_template||"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,b=!1!==s.enable_event_bus,v=s.notification_priority||"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),C=Object.entries(l).map(([e,t])=>{const i=f(t.name||e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=f(e);return`\n \n \n \n \n ${pe(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${pe(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${pe(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${pe(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${s?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,s=o?`${f(o)} (${f(e)})`:f(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===v?n("notification.hint.critical"):"time-sensitive"===v?n("notification.hint.time_sensitive"):"passive"===v?n("notification.hint.passive"):"active"===v?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${C}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const k=e.querySelector("#toggle-all-circuits");k&&!S&&$&&(k.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),s=e.querySelector("#global-status"),a=()=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){s.textContent=`${n("error.prefix")} ${e.message||n("error.failed_save")}`,s.style.color="var(--error-color, #f44336)"}},500)};i&&i.addEventListener("change",async()=>{const s=i.checked;o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none";const a=e.querySelector("#global-status");try{if(s){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void(a&&(a.textContent=`${n("error.prefix")} ${e.message||n("error.failed")}`,a.style.color="var(--error-color, #f44336)"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",a)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),s=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const a=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);s.textContent=i.length?i.join(", "):n("notification.none_selected"),clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},500)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),a=e.querySelector("#g-message-template"),r=(e,n)=>{clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},500)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),a&&a.addEventListener("input",()=>{r("notification_message_template",a.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const s=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`;clearTimeout(n.get(o)),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[l]:o,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,s="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(a,s,r),await this.render(e,t)})}}function ue(e){return Object.keys(s).map(t=>``).join("")}const ge="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class me{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}stop(){for(const e of this._debounceTimers.values())clearTimeout(e);this._debounceTimers.clear()}async render(e,t,i,s){let r;void 0!==i&&(this._configEntryId=i),void 0!==s&&(this._deviceId=s);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${a}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=f(t.name||e),o=d[e]||{},s=o.horizon??l,a=!0===o.has_override,r=f(e);return`\n \n ${i}\n \n \n \n \n ${a?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${a}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${a}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:a,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`;clearTimeout(this._debounceTimers.get(o)),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:a,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},500))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:a,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class _e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config={},this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new se,this._monitoringTab=new he,this._settingsTab=new me}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._monitoringTab.stop(),this._settingsTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry()}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this._hass?.connection&&(this._deviceRegistryUnsub=this._hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this._hass||!this._discovered)return;const e=(await this._hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=new Set(this._panels.map(e=>e.id)),n=new Set(e.map(e=>e.id));t.size===n.size&&[...t].every(e=>n.has(e))||(this._panels=e,!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0&&(this._selectedPanelId=this._panels[0].id,localStorage.setItem("span_panel_selected",this._selectedPanelId)),this._render())}set hass(e){const t=!this._hass&&e;this._hass=e,this._dashboardTab._hass=e;const n=this.shadowRoot.querySelector("ha-menu-button");n&&(n.hass=e),this._discovered||this._discoverPanels(),t&&this._subscribeDeviceRegistry()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){this._config=e||{}}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=t[i]?i:"en",this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n \n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const o=this.shadowRoot.querySelector("ha-menu-button");o&&(o.hass=this._hass,o.narrow=this._narrow);const s=this.shadowRoot.getElementById("panel-select");s&&s.addEventListener("change",()=>{this._selectedPanelId=s.value,localStorage.setItem("span_panel_selected",s.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig(),n=this._panels.find(e=>e.id===this._selectedPanelId),i=n?.config_entries?.[0]||null;await this._dashboardTab.render(e,this._hass,this._selectedPanelId,t,i);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._monitoringTab.render(e,this._hass,n);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]||null;await this._settingsTab.render(e,this._hass,n,this._selectedPanelId);break}}}}customElements.get("span-panel")||customElements.define("span-panel",_e),console.warn("%c SPAN-PANEL %c v0.9.0 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); +!function(){"use strict";let e="en";const t={en:{"tab.panel":"Panel","tab.monitoring":"Monitoring","tab.settings":"Settings","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.persistent":"Persistent Alerts","notification.persistent_hint":"Create persistent HA notifications","notification.event_bus":"Event Bus","notification.event_bus_hint":"Fire events on the HA event bus","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Always On","shedding.never":"Never","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Off-Grid","shedding.unknown":"Unknown"},es:{"tab.panel":"Panel","tab.monitoring":"Monitoreo","tab.settings":"Configuración","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Crear notificaciones persistentes en HA","notification.event_bus":"Bus de Eventos","notification.event_bus_hint":"Emitir eventos en el bus de eventos de HA","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Siempre Encendido","shedding.never":"Nunca","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Fuera de Red","shedding.unknown":"Desconocido"},fr:{"tab.panel":"Panneau","tab.monitoring":"Surveillance","tab.settings":"Paramètres","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.persistent":"Alertes Persistantes","notification.persistent_hint":"Créer des notifications persistantes HA","notification.event_bus":"Bus d'Événements","notification.event_bus_hint":"Émettre des événements sur le bus d'événements HA","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Toujours Actif","shedding.never":"Jamais","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Hors Réseau","shedding.unknown":"Inconnu"},ja:{"tab.panel":"パネル","tab.monitoring":"モニタリング","tab.settings":"設定","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.persistent":"永続アラート","notification.persistent_hint":"HA永続通知を作成","notification.event_bus":"イベントバス","notification.event_bus_hint":"HAイベントバスでイベントを発行","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"常時オン","shedding.never":"なし","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"オフグリッド","shedding.unknown":"不明"},pt:{"tab.panel":"Painel","tab.monitoring":"Monitoramento","tab.settings":"Configurações","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.persistent":"Alertas Persistentes","notification.persistent_hint":"Criar notificações persistentes no HA","notification.event_bus":"Barramento de Eventos","notification.event_bus_hint":"Emitir eventos no barramento de eventos do HA","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Sempre Ligado","shedding.never":"Nunca","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Fora da Rede","shedding.unknown":"Desconhecido"}};function n(n){return t[e]?.[n]??t.en?.[n]??n}const i="power",o="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",r="CLOSED",c="pv",l="bess",d="evse",p="sub_",h=500,u={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},g={soc:{entityRole:"soc",label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:u.power},m={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},_="#ff9800",f={"&":"&","<":"<",">":">",'"':""","'":"'"};function v(e){return String(e).replace(/[&<>"']/g,e=>f[e]??e)}const b=Object.keys(m).filter(e=>"unknown"!==e);class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open","")}close(){this.removeAttribute("open"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;if(!t)return;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n .error-msg {\n color: var(--error-color, #f44336);\n font-size: 0.8em;\n padding: 8px;\n margin: 8px 0;\n background: rgba(244, 67, 54, 0.1);\n border-radius: 4px;\n }\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const o=document.createElement("div");o.className="panel",t.appendChild(o),e.panelMode?this._renderPanelMode(o):e.subDeviceMode?this._renderSubDeviceMode(o,e):this._renderCircuitMode(o,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const a=document.createElement("div");a.className="panel-body";const r=document.createElement("div");r.className="error-msg",r.id="error-msg",r.style.display="none",a.appendChild(r);const c=t.graphSettings,l=t.topology,d=c?.global_horizon??o,p=c?.circuits??{},u=document.createElement("div");u.className="section";const g=document.createElement("div");g.className="section-label",g.textContent=n("sidepanel.graph_horizon"),u.appendChild(g);const m=document.createElement("div");m.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=n("sidepanel.global_default"),m.appendChild(_);const f=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===d&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._callDomainService("set_graph_time_horizon",{horizon:f.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),m.appendChild(f),u.appendChild(m),a.appendChild(u),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.circuit_scales"),e.appendChild(t);const i=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=p[t]||{horizon:d,has_override:!1},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,h,()=>{this._callDomainService("set_circuit_graph_horizon",{circuit_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_circuit_graph_horizon",{circuit_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}const v=c?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.subdevice_scales"),e.appendChild(t);const i=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,o]of i){const i=document.createElement("div");i.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=o.name||t,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",i.appendChild(a);const r=v[t]||{horizon:d,has_override:!1},c=r.has_override?r.horizon:d,l=document.createElement("select");l.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e,t.textContent=n(`horizon.${e}`)||e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,h,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:t,horizon:l.value}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))})}),i.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:t}).then(()=>{l.value=d,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${e.message??e}`))}),i.appendChild(e)}e.appendChild(i)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${v(String(t.breaker_rating_a))}A · ${v(String(t.voltage))}V · Tabs [${v(String(t.tabs))}]`,i=this._createHeader(v(t.name),n);e.appendChild(i);const o=document.createElement("div");o.className="panel-body",e.appendChild(o);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",o.appendChild(s),this._renderRelaySection(o,t),this._renderSheddingSection(o,t),this._renderGraphHorizonSection(o,t),this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(v(t.name),v(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const o=document.createElement("div");o.className="error-msg",o.id="error-msg",o.style.display="none",i.appendChild(o),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),o=v(e),s=v(t);i.innerHTML=`
${o}
`+(s?`
${s}
`:"");const a=document.createElement("button");return a.className="close-btn",a.innerHTML="✕",a.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(a),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.breaker");const a=document.createElement("ha-switch");a.dataset.role="relay-toggle";const r=t.entities.switch,c=this._hass?.states?.[r]?.state;"on"===c&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const e=a.hasAttribute("checked")||a.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:r}).catch(e=>this._showError(`${n("sidepanel.relay_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const o=document.createElement("div");o.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const r=t.entities.select,c=this._hass?.states?.[r]?.state||"";for(const e of b){const t=m[e];if(!t)continue;const n=document.createElement("option");n.value=e,n.textContent=t.label(),e===c&&(n.selected=!0),a.appendChild(n)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:r,option:a.value}).catch(e=>this._showError(`${n("sidepanel.shedding_failed")} ${e.message??e}`))}),o.appendChild(s),o.appendChild(a),i.appendChild(o),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const a=document.createElement("div");a.className="section-label",a.textContent=n("sidepanel.graph_horizon"),i.appendChild(a);const r=t.graphHorizonInfo,c=!0===r?.has_override,l=r?.horizon||o,d=r?.globalHorizon||o,p=document.createElement("div");p.className="horizon-bar";const h=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(s))h.push({key:e,label:e});const u=c?l:"global",g=e=>{for(const t of p.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of h){const o=document.createElement("button");o.type="button",o.className="horizon-segment",o.dataset.horizon=e,o.textContent=i,o.classList.toggle("active",e===u),o.classList.toggle("referenced","global"===u&&e===d),o.addEventListener("click",()=>{if(o.classList.contains("active"))return;const i=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:i}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${n("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),p.appendChild(o)}i.appendChild(p),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const o=document.createElement("div");o.className="monitoring-header";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.monitoring"),s.style.margin="0";const a=document.createElement("ha-switch");a.dataset.role="monitoring-toggle";const r=t.monitoringInfo,c=null!=r&&!1!==r.monitoring_enabled;c&&a.setAttribute("checked",""),o.appendChild(s),o.appendChild(a),i.appendChild(o);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",i.appendChild(l);const d=void 0!==r?.continuous_threshold_pct,p=document.createElement("div");p.className="radio-group",p.innerHTML=`\n \n \n `,l.appendChild(p);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=d?"block":"none";const u=r?.continuous_threshold_pct??80,g=r?.spike_threshold_pct??100,m=r?.window_duration_m??15,_=r?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),h.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),h.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",m,1,180,"m",t)),h.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),l.appendChild(h),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const i=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:i,monitoring_enabled:e}).catch(e=>this._showError(`${n("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const f=p.querySelectorAll('input[type="radio"]');for(const e of f)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(h.style.display=i?"block":"none",!i&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${n("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(i)}_createThresholdRow(e,t,i,o){const s=document.createElement("div");s.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=e;const r=document.createElement("input");return r.type="number",r.min="0",r.max="200",r.value=String(i),r.dataset.role=`threshold-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,h,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),i=e.querySelector('[data-role="threshold-spike"]'),s=e.querySelector('[data-role="threshold-window-m"]'),a=e.querySelector('[data-role="threshold-cooldown-m"]'),r=o.entities?.power||o.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:r,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:s?Number(s.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),s.appendChild(a),s.appendChild(r),s}_createDurationRow(e,t,i,o,s,a,r,c=!1){const l=document.createElement("div");l.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const p=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(o),u.max=String(s),u.value=String(i),u.dataset.role=`threshold-${t}`,c&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=a,p.appendChild(u),p.appendChild(g),c||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,h,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),i=e.querySelector('[data-role="threshold-spike"]'),o=e.querySelector('[data-role="threshold-window-m"]');this._callDomainService("set_circuit_threshold",{circuit_id:r.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:o?Number(o.value):void 0}).catch(e=>this._showError(`${n("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),l.appendChild(d),l.appendChild(p),l}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(!e.subDeviceMode){if(e.entities?.switch){const t=this.shadowRoot?.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot?.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._hass?this._hass.callWS({type:"call_service",domain:a,service:e,service_data:t,return_response:!0}):Promise.resolve()}_showError(e){const t=this.shadowRoot?.getElementById("error-msg");t&&(t.textContent=e,t.style.display="block",setTimeout(()=>{t.style.display="none"},5e3))}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}async function x(e,t){if(!t)throw new Error(n("card.device_not_found"));const i=await e.callWS({type:`${a}/panel_topology`,device_id:t}),o=i.panel_size??function(e){let t=0;for(const n of Object.values(e))if(n)for(const e of n.tabs)e>t&&(t=e);return t>0?t+t%2:0}(i.circuits);if(!o)throw new Error(n("card.topology_error"));const s=await e.callWS({type:"config/device_registry/list"});var r;return{topology:i,panelDevice:(r=s.find(e=>e.id===t))?{id:r.id,name:r.name,name_by_user:r.name_by_user,config_entries:r.config_entries,identifiers:r.identifiers,via_device_id:r.via_device_id,sw_version:r.sw_version,model:r.model}:null,panelSize:o}}customElements.get("span-side-panel")||customElements.define("span-side-panel",y);const w=u.power;function S(e){return w.unit(e)}function $(e){return(e<0?"-":"")+w.format(e)}function z(e){return(Math.abs(e)/1e3).toFixed(1)}function C(e){return Math.ceil(e/2)}function k(e){return e%2==0?1:0}function E(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return C(t)===C(n)?"row-span":k(t)===k(n)?"col-span":"row-span"}function P(e){const t=e.chart_metric??i;return u[t]??u[i]}function M(e,t){const n=function(e){return P(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class N{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=o?.response??null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function A(e,t,i,o,s,a,l,d,p){const h=t.entities?.power,u=h?a.states[h]:null,g=u&&parseFloat(u.state)||0,f=t.device_type===c||g<0,b=t.entities?.switch,y=b?a.states[b]:null,x=y?"on"===y.state:(u?.attributes?.relay_state||t.relay_state)===r,w=t.breaker_rating_a,z=w?`${Math.round(w)}A`:"",C=v(t.name||n("grid.unknown")),k=P(l);let E;if("current"===k.entityRole){const e=t.entities?.current,n=e?a.states[e]:null,i=n&&parseFloat(n.state)||0;E=`${k.format(i)}A`}else E=`${$(g)}${S(g)}`;const M=m[p||"unknown"]??m.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};let N;N=M.icon2?`\n \n \n `:M.textLabel?`\n \n ${M.textLabel}\n `:``;const A=d&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(d),T=A?_:"#555",L=``;let D="";if(null!=d?.utilization_pct){const e=d.utilization_pct,t=function(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}(d);D=`${Math.round(e)}%`}const I=function(e){return!!e&&null!=e.over_threshold_since}(d);return`\n
\n
\n
\n ${z?`${z}`:""}\n ${C}\n
\n
\n \n ${E}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(x?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${N}\n ${D}\n ${L}\n
\n
\n
\n `}function T(e,t){return`\n
\n \n
\n `}const L={names:["power","battery power"],suffixes:["_power"]},D={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},I={names:["state of energy"],suffixes:["_soe_kwh"]},H={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function R(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name??"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function q(e){return R(e,L)}function j(e){return R(e,D)}function G(e){return R(e,I)}function O(e){return R(e,H)}function F(e,t,n,i){const o=n.visible_sub_entities||{};let s="";if(!e.entities)return s;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==o[n])continue;const r=t.states[n];if(!r)continue;let c=a.original_name||r.attributes.friendly_name||n;const l=e.name||"";let d;if(c.startsWith(l+" ")&&(c=c.slice(l.length+1)),t.formatEntityState)d=t.formatEntityState(r);else{d=r.state;const e=r.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}s+=`\n
\n ${v(c)}:\n ${v(d)}\n
\n `}return s}function W(e,t,i,o,s,a){if(i){return`\n
\n ${[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!s},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!o}].filter(e=>e.available).map(e=>`\n
\n
${v(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function B(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(String(e.history_days))||0)+(t&&parseInt(String(e.history_hours))||0))+(t?parseInt(String(e.history_minutes))||0:5))*1e3;return Math.max(n,6e4)}function V(e){const t=s[e];return t?t.ms:s[o].ms}function U(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function X(e){return Math.max(500,Math.floor(e/5e3))}function J(e,t,n,i,o,s){e.has(t)||e.set(t,[]);const a=e.get(t);a.push({time:i,value:n});const r=a.findIndex(e=>e.time>=o);r>0?a.splice(0,r):-1===r&&(a.length=0),a.length>s&&a.splice(0,a.length-s)}function K(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}async function Q(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:s,statistic_ids:t,period:a,types:["mean"]});for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];t.sort((e,t)=>e.time-t.time),o.set(i,t)}}}async function Y(e,t,n,i,o){const s=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:s,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=U(i),c=X(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const s=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&s.push({time:n,value:t})}if(s.length>0){const e=o.get(i)||[],t=[...s,...e];o.set(i,K(t,r,c))}}}function Z(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:q(i)};i.type===l&&(e.soc=j(i),e.soe=G(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${p}${n}_${i}`,devId:n})}return t}async function ee(e,t,n,i,o,s){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=M(i,n);if(!t)continue;let s;s=o&&o.has(e)?V(o.get(e)):B(n),a.has(s)||a.set(s,{entityIds:[],uuidByEntity:new Map});const r=a.get(s);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:o}of Z(t)){let t;t=s&&s.has(o)?V(s.get(o)):B(n),a.has(t)||a.set(t,{entityIds:[],uuidByEntity:new Map});const r=a.get(t);r.entityIds.push(e),r.uuidByEntity.set(e,i)}const r=[];for(const[t,n]of a){if(0===n.entityIds.length)continue;t>2592e5?r.push(Q(e,n.entityIds,n.uuidByEntity,t,i)):r.push(Y(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function te(e,t,n,o,s,a,r,c){const{options:l,series:d}=function(e,t,n,o,s){n||(n=u[i]);const a=o?"140, 160, 220":"77, 217, 175",r=`rgb(${a})`,c=Date.now(),l=c-t,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=n.unit(0),h=(e??[]).filter(e=>e.time>=l).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:h,showSymbol:!1,smooth:!1,lineStyle:{width:1.5,color:r},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.35)`},{offset:1,color:`rgba(${a}, 0.02)`}]}},itemStyle:{color:r}}],m=h.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(h):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:m<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};return d?(_.min=n.fixedMin,_.max=n.fixedMax):m<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),g.push({type:"line",data:[[l,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),g.push({type:"line",data:[[l,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}})),{options:{xAxis:{type:"time",min:l,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||0===e.length)return"";const t=e[0];return`
${new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
${parseFloat(t.value[1].toFixed(2))} ${p}
`}},animation:!1},series:g}}(n,o,s,a,c);let p=e.querySelector("ha-chart-base");p||(p=document.createElement("ha-chart-base"),p.style.display="block",p.style.width="100%",p.height=(r??120)+"px",e.innerHTML="",e.appendChild(p)),p.hass=t,p.options=l,p.data=d}function ne(e,t,i,o,s,a){if(!e||!i||!t)return;const l=B(o);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],o=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(o))}!function(e,t,n,i,o){const s="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(s){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,o=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(o)?Math.abs(o).toFixed(1):"--"),r&&(r.textContent="A")}else{const e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(o=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=z(o)),r&&(r.textContent="kW")}const c=e.querySelector(".stat-upstream .stat-value"),l=e.querySelector(".stat-upstream .stat-unit");if(c){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;c.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",l&&(l.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;c.textContent=z(e),l&&(l.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),p=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",p&&(p.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=z(e),p&&(p.textContent="kW")}}const h=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(h){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(s){const e=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);h.textContent=z(e)}else h.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const m=e.querySelector(".stat-grid-state .stat-value");if(m){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;m.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(e,t,i,o,d);const p=P(o),h="current"===p.entityRole;for(const[o,d]of Object.entries(i.circuits)){const i=e.querySelector(`[data-uuid="${o}"]`);if(!i)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,f=d.device_type===c||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===r,x=i.querySelector(".power-value");if(x)if(h){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${p.format(i)}A`}else x.innerHTML=`${$(_)}${S(_)}`;const w=i.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=w.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let z;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",f),d.always_on)z="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;z=n?n.state:"unknown"}const C=m[z]??m.unknown,k=i.querySelector(".shedding-icon");k&&(k.setAttribute("icon",C.icon),k.style.color=C.color,k.title=C.label());const E=i.querySelector(".shedding-icon-secondary");E&&(C.icon2?(E.setAttribute("icon",C.icon2),E.style.color=C.color,E.style.display=""):E.style.display="none");const P=i.querySelector(".shedding-label");P&&(C.textLabel?(P.textContent=C.textLabel,P.style.color=C.color,P.style.display=""):P.style.display="none");const M=i.querySelector(".chart-container");if(M){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100;te(M,t,e,a?.has(o)?V(a.get(o)):l,p,f,n,d.breaker_rating_a??void 0)}}}class ie{constructor(){this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const o=await e.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=o?.response??null,this._lastFetch=n}catch{this._settings=null}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}function oe(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function se(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class ae{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new N,this.graphSettingsCache=new ie,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._updateInterval=null,this._recorderRefreshInterval=null,this._resizeObserver=null,this._lastWidth=0,this._resizeDebounce=null}get hass(){return this._hass}set hass(e){this._hass=e}get topology(){return this._topology}get config(){return this._config}init(e,t,n,i){this._topology=e,this._config=t,this._hass=n,this._configEntryId=i}setConfig(e){this._config=e}buildHorizonMaps(e){if(e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits))this.horizonMap.set(t,oe(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,se(e,t))}async fetchAndBuildHorizonMaps(){try{await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings)}catch{}}async loadHistory(){await ee(this._hass,this._topology,this._config,this.powerHistory,this.horizonMap,this.subDeviceHorizonMap)}recordSamples(){if(!this._topology||!this._hass||!this._config)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this.horizonMap.get(t)??o;if(!s[i]?.useRealtime)continue;const a=M(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const c=parseFloat(r.state);if(isNaN(c))continue;const l=V(i),d=U(l),p=X(l),h=e-l,u=this.powerHistory.get(t)??[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time${$(i)} ${S(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey;if(!n)continue;const r=o.get(n)||[];let c=g.power;n.endsWith("_soc")?c=g.soc:n.endsWith("_soe")&&(c=g.soe);const l=!!e.closest(".bess-chart-col");te(e,t,r,s?.has(i)?V(s.get(i)):a,c,!1,l?120:150)}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const o=t.states[e];if(o){const e=o.attributes.unit_of_measurement;i.textContent=`${o.state}${e?" "+e:""}`}}}}(e,this._hass,this._topology,this._config,this.powerHistory,this.subDeviceHorizonMap))}async onGraphSettingsChanged(e){if(this._hass){this.graphSettingsCache.invalidate(),await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings),this.powerHistory.clear();try{await this.loadHistory()}catch{}this.updateDOM(e)}}onToggleClick(e,t){const n=e.target,i=n?.closest(".toggle-pill");if(!i)return;const o=t.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const s=i.closest("[data-uuid]");if(!s||!this._topology||!this._hass)return;const a=s.dataset.uuid;if(!a)return;const r=this._topology.circuits[a];if(!r)return;const c=r.entities?.switch;if(!c)return;const l=this._hass.states[c];if(!l)return void console.warn("SPAN Panel: switch entity not found:",c);const d="on"===l.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c}).catch(e=>{console.error("SPAN Panel: switch service call failed:",e)})}async onGearClick(e,t){const n=e.target,i=n?.closest(".gear-icon");if(!i)return;const s=t.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,i.classList.contains("panel-gear"))return await this.graphSettingsCache.fetch(this._hass,this._configEntryId),void s.open({panelMode:!0,topology:this._topology,graphSettings:this.graphSettingsCache.settings});const a=i.dataset.uuid;if(a&&this._topology){const e=this._topology.circuits[a];if(e){await this.monitoringCache.fetch(this._hass,this._configEntryId);const t=e.entities?.power,n=t?this.monitoringCache.status?.circuits?.[t]??null:null;await this.graphSettingsCache.fetch(this._hass,this._configEntryId);const i=this.graphSettingsCache.settings,r=i?.global_horizon??o,c=i?.circuits?.[a],l=c?{...c,globalHorizon:r}:{horizon:r,has_override:!1,globalHorizon:r};return void s.open({...e,uuid:a,monitoringInfo:n,graphHorizonInfo:l})}}const r=i.dataset.subdevId;if(r&&this._topology?.sub_devices?.[r]){const e=this._topology.sub_devices[r];await this.graphSettingsCache.fetch(this._hass,this._configEntryId);const t=this.graphSettingsCache.settings,n=t?.global_horizon??o,i=t?.sub_devices?.[r],a=i?{...i,globalHorizon:n}:{horizon:n,has_override:!1,globalHorizon:n};s.open({subDeviceMode:!0,subDeviceId:r,name:e.name??r,deviceType:e.type??"",graphHorizonInfo:a})}}bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n||!i)return;let o=!1,s=0,a=0;const r=t=>{e.classList.contains("confirmed")||(o=!0,s=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},l=()=>{if(!o)return;o=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",e.classList.add("confirmed"),n.querySelector("ha-icon")?.setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn??"",t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),r(e.clientX)}),e.addEventListener("mousemove",e=>c(e.clientX)),e.addEventListener("mouseup",l),e.addEventListener("mouseleave",l),n.addEventListener("touchstart",e=>{e.preventDefault(),r(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>c(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",l),e.addEventListener("touchcancel",l),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon")?.setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff??"",t&&t.classList.add("switches-disabled"))})}startIntervals(e,t){this._updateInterval=setInterval(()=>{this.recordSamples(),this.updateDOM(e),t&&t()},1e3),this._recorderRefreshInterval=setInterval(()=>{this.refreshRecorderData(e)},3e4)}stopIntervals(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this.cleanupResizeObserver()}setupResizeObserver(e,t){this.cleanupResizeObserver(),t&&(this._lastWidth=t.clientWidth,this._resizeObserver=new ResizeObserver(t=>{const n=t[0];if(!n)return;const i=n.contentRect.width;Math.abs(i-this._lastWidth)<5||(this._lastWidth=i,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}this.updateDOM(e)},150))}),this._resizeObserver.observe(t))}cleanupResizeObserver(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}reset(){this.powerHistory.clear(),this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),this.monitoringCache.clear(),this.graphSettingsCache.clear()}}class re{constructor(){this._ctrl=new ae,this._container=null,this._onSidePanelClosed=null,this._onGraphSettingsChanged=null}get hass(){return this._ctrl.hass}set hass(e){this._ctrl.hass=e}async render(e,t,i,o,s){let a,r;this.stop(),this._container=e,this._ctrl.hass=t;try{const e=await x(t,i);a=e.topology,r=e.panelSize}catch(t){return void(e.innerHTML=`

${t.message}

`)}this._ctrl.init(a,o,t,s??null),await this._ctrl.monitoringCache.fetch(t,s??null),await this._ctrl.fetchAndBuildHorizonMaps();const c=Math.ceil(r/2),p=this._ctrl.monitoringCache.status,h=function(e,t){const i=v(e.device_name||n("header.default_name")),o=v(e.serial||""),s=v(e.firmware||""),a="current"===(t.chart_metric||"power"),r=!!e.panel_entities?.site_power,c=!!e.panel_entities?.dsm_state,l=!!e.panel_entities?.current_power,d=!!e.panel_entities?.feedthrough_power,p=!!e.panel_entities?.pv_power,h=!!e.panel_entities?.battery_level;return`\n
\n
\n
\n

${i}

\n ${o}\n \n
\n ${n("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${n("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${n("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${n("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${s}\n
\n \n \n
\n
\n
\n ${Object.entries(m).filter(([e])=>"unknown"!==e).map(([,e])=>{let t;return t=e.icon2?``:e.textLabel?`${e.textLabel}`:``,`
${t}${e.label()}
`}).join("")}\n
\n
\n
\n `}(a,o),u=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),i=Object.values(e.mains??{}),o=[...t,...i],s=o.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=80&&e.utilization_pct<100).length,a=o.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=100).length,r=o.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${s>0?`${s} ${n(s>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${n(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${n(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(p),g=function(e,t,n,i,o){const s=new Map,a=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),o=1===e.length?"single":E(e)??"single";s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)a.add(t)}const r=new Set,c=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=C(Math.max(...n));0===k(e)?r.add(i):c.add(i)}function l(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=o?(s=o,a=t??"",s?.circuits?s.circuits[a]??null:null):null;var s,a;let r;if(e.circuit.always_on)r="always_on";else{const t=e.circuit.entities?.select;r=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:r}}let d="";for(let e=1;e<=t;e++){const t=2*e-1,o=2*e,p=s.get(t),h=s.get(o);if(d+=`
${t}
`,p&&"row-span"===p.layout){const{monInfo:t,sheddingPriority:s}=l(p);d+=A(p.uuid,p.circuit,e,"2 / 4","row-span",n,i,t,s),d+=`
${o}
`;continue}if(!r.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)a.has(t)||(d+=T(e,"2"));else{const{monInfo:t,sheddingPriority:o}=l(p);d+=A(p.uuid,p.circuit,e,"2",p.layout,n,i,t,o)}if(!c.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)a.has(o)||(d+=T(e,"3"));else{const{monInfo:t,sheddingPriority:o}=l(h);d+=A(h.uuid,h.circuit,e,"3",h.layout,n,i,t,o)}d+=`
${o}
`}return d}(a,c,t,o,p),_=function(e,t,i){const o=!1!==i.show_battery,s=!1!==i.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===l&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let c=0,p="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===l?n("subdevice.battery"):n("subdevice.fallback"),a=q(o),h=a?t.states[a]:void 0,u=h&&parseFloat(h.state)||0,g=o.type===l,m=o.type===d,_=g?j(o):null,f=g?G(o):null,b=g?O(o):null,y=F(o,t,i,new Set([a,_,f,b].filter(e=>null!==e))),x=W(e,0,g,a,_,f);let w="";g?w="sub-device-bess":m&&(c++,c===r&&r%2==1&&(w="sub-device-full")),p+=`\n
\n
\n ${v(s)}\n ${v(o.name||"")}\n ${a?`${$(u)} ${S(u)}`:""}\n \n
\n ${x}\n ${y}\n
\n `}return p}(a,t,o);e.innerHTML=`\n \n ${h}\n ${u}\n ${_?`
${_}
`:""}\n ${!1!==o.show_panel?`\n
\n ${g}\n
\n `:""}\n \n `,e.addEventListener("click",t=>this._ctrl.onGearClick(t,e)),e.addEventListener("click",t=>this._ctrl.onToggleClick(t,e)),this._onSidePanelClosed=()=>{this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()},e.addEventListener("side-panel-closed",this._onSidePanelClosed),this._onGraphSettingsChanged=()=>this._ctrl.onGraphSettingsChanged(e),e.addEventListener("graph-settings-changed",this._onGraphSettingsChanged);try{await this._ctrl.loadHistory()}catch{}this._ctrl.updateDOM(e);const f=e.querySelector(".slide-confirm");f&&(this._ctrl.bindSlideConfirm(f,e),e.classList.add("switches-disabled")),this._ctrl.setupResizeObserver(e,e),this._ctrl.startIntervals(e)}stop(){this._ctrl.stopIntervals(),this._container&&this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._container&&this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null)}}const ce="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",le="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",de="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",pe="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",he="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function ue(e,t,n,i,o){return`\n ${i}\n `}class ge{constructor(){this._debounceTimer=null,this._configEntryId=null,this._notifyCloseHandler=null}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i){let o;void 0!==i&&(this._configEntryId=i),this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=n?.response||null}catch{o=null}const s=o?.global_settings??{},r=!0===o?.enabled,c=o?.circuits??{},l=o?.mains??{},d=new Set;for(const[e,n]of Object.entries(t.states||{})){if(!e.startsWith("person."))continue;const t=n.attributes?.device_trackers;if(t)for(const e of t){const t=e.split(".")[1];t&&d.add(`notify.mobile_app_${t}`)}}for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);for(const e of Object.keys(t.services?.notify||{}))d.add(`notify.${e}`);const p=[...d].sort(),h=s.notify_targets??"notify.notify",u=("string"==typeof h?h.split(","):h).map(e=>e.trim()).filter(Boolean),g=s.notification_title_template??"SPAN: {name} {alert_type}",m=s.notification_message_template??"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",_=!1!==s.enable_persistent_notifications,f=!1!==s.enable_event_bus,b=s.notification_priority??"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name??"").localeCompare(t.name??"")),x=Object.entries(l),w=[...y,...x],S=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),z=y.map(([e,t])=>{const i=v(t.name??e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=v(e);return`\n \n \n \n \n ${ue(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${ue(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${ue(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${ue(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),C=Object.entries(l).map(([e,t])=>{const i=v(t.name??e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=v(e);return`\n \n \n \n \n ${ue(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${ue(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${ue(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${ue(r,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${s?``:""}\n \n \n `}).join("");e.innerHTML=`\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n
\n \n
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const n=u.includes(e),i=t.states[e],o=i?.attributes?.friendly_name,s=o?`${v(o)} (${v(e)})`:v(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.persistent")}\n \n
\n\n
\n ${n("notification.event_bus")}\n \n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m}\n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${C}\n ${z}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const k=e.querySelector("#toggle-all-circuits");k&&!S&&$&&(k.indeterminate=!0),this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,l),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),o=e.querySelector("#global-fields"),s=e.querySelector("#global-status"),a=()=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){if(s){const t=e instanceof Error?e.message:n("error.failed_save");s.textContent=`${n("error.prefix")} ${t}`,s.style.color="var(--error-color, #f44336)"}}},h)};i&&i.addEventListener("change",async()=>{const s=i.checked;o&&(o.style.opacity=s?"":"0.4",o.style.pointerEvents=s?"":"none");const a=e.querySelector("#global-status");try{if(s){const n={continuous_threshold_pct:parseInt(e.querySelector("#g-continuous").value,10),spike_threshold_pct:parseInt(e.querySelector("#g-spike").value,10),window_duration_m:parseInt(e.querySelector("#g-window").value,10),cooldown_duration_m:parseInt(e.querySelector("#g-cooldown").value,10)};await this._callSetGlobal(t,n)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){if(a){const t=e instanceof Error?e.message:n("error.failed");a.textContent=`${n("error.prefix")} ${t}`,a.style.color="var(--error-color, #f44336)"}return}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",a)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),o=e.querySelector("#notify-target-dropdown"),s=e.querySelector("#notify-target-label");if(!i||!o)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==o.style.display;o.style.display=t?"none":"block"});const a=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(o.style.display="none")};document.addEventListener("click",a),this._notifyCloseHandler=a;for(const i of e.querySelectorAll(".notify-target-cb"))i.addEventListener("change",()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);s&&(s.textContent=i.length?i.join(", "):n("notification.none_selected")),this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},h)})}_bindNotificationSettings(e,t){const n=e.querySelector("#g-persistent-notifications"),i=e.querySelector("#g-event-bus"),o=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),a=e.querySelector("#g-message-template"),r=(e,n)=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:n})}catch{}},h)};n&&n.addEventListener("change",()=>{r("enable_persistent_notifications",n.checked)}),i&&i.addEventListener("change",()=>{r("enable_event_bus",i.checked)}),o&&o.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:o.value}),await this.render(e,t)}catch{}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),a&&a.addEventListener("input",()=>{r("notification_message_template",a.value)})}_bindToggleAll(e,t,n,i){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const s=o.checked,r=[...Object.keys(n).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:s})}).catch(()=>{})),...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:s})}).catch(()=>{}))];await Promise.all(r),await this.render(e,t)})}_bindMainsToggles(e,t){for(const n of e.querySelectorAll(".mains-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await Promise.all([t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:i,monitoring_enabled:o})})])}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const n of e.querySelectorAll(".circuit-toggle"))n.addEventListener("change",async()=>{const i=n.dataset.entity,o=n.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:i,monitoring_enabled:o})})}catch{return void(n.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const n=new Map;for(const i of e.querySelectorAll(".threshold-input"))i.addEventListener("input",()=>{const o=`${i.dataset.entity}-${i.dataset.field}`,s=n.get(o);s&&clearTimeout(s),n.set(o,setTimeout(async()=>{const n=parseInt(i.value,10);if(!n||n<1)return;const o=i.dataset.entity,s=i.dataset.field,r=i.dataset.type,c="mains"===r?"set_mains_threshold":"set_circuit_threshold",l="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[l]:o,[s]:n})}),await this.render(e,t)}catch{i.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity,o=n.dataset.type,s="mains"===o?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===o?{leg:i}:{circuit_id:i});await t.callService(a,s,r),await this.render(e,t)})}}function me(e){return Object.keys(s).map(t=>``).join("")}const _e="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:4px 8px;font-size:0.85em;\n";class fe{constructor(){this._debounceTimers=new Map,this._configEntryId=null,this._deviceId=null}stop(){for(const e of this._debounceTimers.values())clearTimeout(e);this._debounceTimers.clear()}async render(e,t,i,s){let r;void 0!==i&&(this._configEntryId=i),void 0!==s&&(this._deviceId=s);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_graph_settings",service_data:e,return_response:!0});r=n?.response||null}catch{r=null}let c=null;try{this._deviceId&&(c=await t.callWS({type:`${a}/panel_topology`,device_id:this._deviceId}))}catch{c=null}const l=r?.global_horizon??o,d=r?.circuits??{},p=c?Object.entries(c.circuits||{}).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||"")):[],h=p.map(([e,t])=>{const i=v(t.name||e),o=d[e],s=o?.horizon??l,a=!0===o?.has_override,r=v(e);return`\n \n ${i}\n \n \n \n \n ${a?``:""}\n \n \n `}).join(""),u=this._configEntryId?`/config/integrations/integration/${a}#config_entry=${this._configEntryId}`:`/config/integrations/integration/${a}`;e.innerHTML=`\n
\n

${n("settings.heading")}

\n

\n ${n("settings.description")}\n

\n \n ${n("settings.open_link")} →\n \n\n
\n\n

${n("settings.graph_horizon_heading")}

\n\n
\n
\n ${n("settings.global_default")}\n \n
\n
\n\n ${p.length>0?`\n

${n("settings.circuit_graph_scales")}

\n \n \n \n \n \n \n \n \n \n ${h}\n \n
${n("settings.col.circuit")}${n("settings.col.scale")}
\n `:""}\n
\n `,this._bindGlobalHorizon(e,t),this._bindCircuitHorizons(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_bindGlobalHorizon(e,t){const n=e.querySelector("#global-horizon");n&&n.addEventListener("change",async()=>{await t.callWS({type:"call_service",domain:a,service:"set_graph_time_horizon",service_data:this._serviceData({horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}_bindCircuitHorizons(e,t){for(const n of e.querySelectorAll(".horizon-select"))n.addEventListener("change",()=>{const i=n.dataset.circuit,o=`circuit-${i}`,s=this._debounceTimers.get(o);s&&clearTimeout(s),this._debounceTimers.set(o,setTimeout(async()=>{await t.callWS({type:"call_service",domain:a,service:"set_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i,horizon:n.value})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)},h))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.circuit;await t.callWS({type:"call_service",domain:a,service:"clear_circuit_graph_horizon",service_data:this._serviceData({circuit_id:i})}),e.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0})),await this.render(e,t)})}}class ve extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._hass=null,this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._narrow=!1,this._dashboardTab=new re,this._monitoringTab=new ge,this._settingsTab=new fe,this._onVisibilityChange=null,this._deviceRegistryUnsub=null}connectedCallback(){this._discovered&&this._hass&&this._render(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this._hass&&this._renderTab()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._monitoringTab.stop(),this._settingsTab.stop(),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry()}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this._hass?.connection&&(this._deviceRegistryUnsub=this._hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this._hass||!this._discovered)return;const e=(await this._hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=new Set(this._panels.map(e=>e.id)),n=new Set(e.map(e=>e.id));t.size===n.size&&[...t].every(e=>n.has(e))||(this._panels=e,!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0&&(this._selectedPanelId=this._panels[0].id,localStorage.setItem("span_panel_selected",this._selectedPanelId)),this._render())}set hass(e){const t=!this._hass&&e;this._hass=e,this._dashboardTab.hass=e;const n=this.shadowRoot.querySelector("ha-menu-button");n&&(n.hass=e),this._discovered||this._discoverPanels(),t&&this._subscribeDeviceRegistry()}set narrow(e){this._narrow=e;const t=this.shadowRoot.querySelector("ha-menu-button");t&&(t.narrow=e)}setConfig(e){}async _discoverPanels(){if(!this._hass)return;this._discovered=!0;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id);const t=localStorage.getItem("span_panel_selected");t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power",this._render()}_render(){var i;i=this._hass?.language,e=i&&t[i]?i:"en",this.shadowRoot.innerHTML=`\n \n\n
\n
\n \n
\n \n \n \n
\n
\n\n
\n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n `;const o=this.shadowRoot.querySelector("ha-menu-button");o&&(o.hass=this._hass,o.narrow=this._narrow);const s=this.shadowRoot.getElementById("panel-select");s&&s.addEventListener("change",()=>{this._selectedPanelId=s.value,localStorage.setItem("span_panel_selected",s.value),this._renderTab()});for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.addEventListener("click",()=>{this._activeTab=e.dataset.tab;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===this._activeTab);this._renderTab()});this._bindUnitToggle(),this._bindTabNavigation(),this.shadowRoot.addEventListener("graph-settings-changed",()=>{"settings"===this._activeTab&&this._renderTab()}),this._renderTab()}_bindUnitToggle(){this.shadowRoot.addEventListener("click",e=>{const t=e.target.closest(".unit-btn");if(!t)return;const n=t.dataset.unit;n&&n!==this._chartMetric&&(this._chartMetric=n,localStorage.setItem("span_panel_metric",n),"dashboard"===this._activeTab&&this._renderTab())})}_bindTabNavigation(){this.shadowRoot.addEventListener("navigate-tab",e=>{const t=e.detail;if(t){this._activeTab=t;for(const e of this.shadowRoot.querySelectorAll(".panel-tab"))e.classList.toggle("active",e.dataset.tab===t);this._renderTab()}})}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _renderTab(){this._dashboardTab.stop();const e=this.shadowRoot.getElementById("tab-content");if(e)switch(this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig(),n=this._panels.find(e=>e.id===this._selectedPanelId),i=n?.config_entries?.[0]??null;await this._dashboardTab.render(e,this._hass,this._selectedPanelId??"",t,i);break}case"monitoring":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]??null;await this._monitoringTab.render(e,this._hass,n??void 0);break}case"settings":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]??null;await this._settingsTab.render(e,this._hass,n??void 0,this._selectedPanelId??void 0);break}}}}customElements.get("span-panel")||customElements.define("span-panel",ve),console.warn("%c SPAN-PANEL %c v0.9.0 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;")}(); diff --git a/eslint.config.js b/eslint.config.js index 3db8d72..69f149b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,7 +1,9 @@ import js from "@eslint/js"; +import tseslint from "typescript-eslint"; -export default [ +export default tseslint.config( js.configs.recommended, + ...tseslint.configs.recommended, { languageOptions: { ecmaVersion: "latest", @@ -29,19 +31,29 @@ export default [ }, }, rules: { - "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], "no-console": ["warn", { allow: ["warn", "error"] }], + eqeqeq: ["error", "always", { null: "ignore" }], + "no-var": "error", + "prefer-const": "error", + "no-shadow": "off", + "@typescript-eslint/no-shadow": "error", + "consistent-return": "error", }, }, { - files: ["rollup.config.mjs", "eslint.config.js"], + files: ["rollup.config.mjs", "eslint.config.js", "scripts/**/*.mjs"], languageOptions: { globals: { process: "readonly", }, }, + rules: { + "@typescript-eslint/no-require-imports": "off", + }, }, { - ignores: ["dist/", "node_modules/", "span-panel-card.js"], - }, -]; + ignores: ["dist/", "node_modules/", "span-panel-card.js", "tests/"], + } +); diff --git a/lefthook.yml b/lefthook.yml index b9d301a..9ff632d 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,15 +2,18 @@ pre-commit: parallel: true jobs: - name: prettier - glob: "*.{js,mjs,json,md,yml,yaml}" + glob: "*.{ts,js,mjs,json,md,yml,yaml}" run: npx prettier --check {staged_files} stage_fixed: true - name: eslint - glob: "*.{js,mjs}" + glob: "*.{ts,js,mjs}" run: npx eslint {staged_files} + - name: typecheck + glob: "src/**/*.ts" + run: npx tsc --noEmit - name: markdownlint glob: "*.md" run: npx markdownlint-cli2 {staged_files} - name: i18n-validate - glob: "src/**/*.{js,mjs}" + glob: "src/**/*.ts" run: node scripts/validate-i18n.mjs diff --git a/package-lock.json b/package-lock.json index d2c5d7f..0e7bb73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,62 @@ { "name": "span-panel-card", - "version": "0.8.5", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "span-panel-card", - "version": "0.8.5", + "version": "0.9.0", "devDependencies": { "@eslint/js": "^10.0.1", "@evilmartians/lefthook": "^2.1.3", "@rollup/plugin-terser": "^0.4.0", + "@rollup/plugin-typescript": "^12.3.0", "eslint": "^10.0.3", "markdownlint-cli2": "^0.21.0", "prettier": "^3.8.1", - "rollup": "^4.0.0" + "rollup": "^4.0.0", + "tslib": "^2.8.1", + "typescript": "^6.0.2", + "typescript-eslint": "^8.58.0", + "vitest": "^4.1.2" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -268,6 +310,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -306,47 +367,20 @@ "node": ">= 8" } }, - "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", - "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", "dev": true, "license": "MIT", - "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", "cpu": [ "arm64" ], @@ -355,12 +389,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", "cpu": [ "arm64" ], @@ -369,12 +406,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", "cpu": [ "x64" ], @@ -383,26 +423,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", "cpu": [ "x64" ], @@ -411,26 +440,15 @@ "optional": true, "os": [ "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", "cpu": [ "arm" ], @@ -439,166 +457,186 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" + "libc": [ + "musl" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", "cpu": [ - "ppc64" + "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", "cpu": [ - "riscv64" + "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", "cpu": [ - "riscv64" + "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", "cpu": [ - "x64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", "cpu": [ "x64" ], @@ -606,8 +644,384 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" - ] + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", + "integrity": "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, "node_modules/@rollup/rollup-openharmony-arm64": { "version": "4.59.0", @@ -692,6 +1106,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -702,6 +1145,13 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -744,6 +1194,349 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", + "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", + "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.2", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", + "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", + "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.2", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", + "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.2", + "@vitest/utils": "4.1.2", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", + "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", + "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.2", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -802,7 +1595,17 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "Python-2.0" + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } }, "node_modules/balanced-match": { "version": "4.0.4", @@ -847,6 +1650,16 @@ "dev": true, "license": "MIT" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -887,6 +1700,13 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -951,6 +1771,16 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -978,6 +1808,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1156,6 +1993,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1166,6 +2010,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1293,6 +2147,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-east-asian-width": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", @@ -1340,6 +2204,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -1386,6 +2263,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -1553,6 +2446,279 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1579,6 +2745,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/markdown-it": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", @@ -2249,6 +3425,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2256,6 +3451,17 @@ "dev": true, "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2346,6 +3552,27 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2359,6 +3586,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2436,6 +3692,27 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -2447,6 +3724,40 @@ "node": ">=0.10.0" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + } + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -2537,6 +3848,19 @@ ], "license": "MIT" }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -2570,6 +3894,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -2603,6 +3934,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -2614,6 +3955,20 @@ "source-map": "^0.6.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", @@ -2647,6 +4002,19 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/terser": { "version": "5.46.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", @@ -2666,6 +4034,81 @@ "node": ">=10" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2679,6 +4122,26 @@ "node": ">=8.0" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2692,6 +4155,44 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -2722,6 +4223,192 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", + "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.2", + "@vitest/mocker": "4.1.2", + "@vitest/pretty-format": "4.1.2", + "@vitest/runner": "4.1.2", + "@vitest/snapshot": "4.1.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.2", + "@vitest/browser-preview": "4.1.2", + "@vitest/browser-webdriverio": "4.1.2", + "@vitest/ui": "4.1.2", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2738,6 +4425,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 7f9336e..d796eda 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,25 @@ "private": true, "type": "module", "scripts": { - "build": "rollup -c", - "dev": "rollup -c -w" + "build": "tsc --noEmit && rollup -c", + "dev": "rollup -c -w", + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "test": "vitest run", + "test:watch": "vitest" }, "devDependencies": { "@eslint/js": "^10.0.1", "@evilmartians/lefthook": "^2.1.3", "@rollup/plugin-terser": "^0.4.0", + "@rollup/plugin-typescript": "^12.3.0", "eslint": "^10.0.3", "markdownlint-cli2": "^0.21.0", "prettier": "^3.8.1", - "rollup": "^4.0.0" + "rollup": "^4.0.0", + "tslib": "^2.8.1", + "typescript": "^6.0.2", + "typescript-eslint": "^8.58.0", + "vitest": "^4.1.2" } } diff --git a/rollup.config.mjs b/rollup.config.mjs index e27cd67..58a9079 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,11 +1,12 @@ import terser from "@rollup/plugin-terser"; +import typescript from "@rollup/plugin-typescript"; const dev = process.env.ROLLUP_WATCH === "true"; -const plugins = dev ? [] : [terser()]; +const plugins = [typescript(), ...(dev ? [] : [terser()])]; export default [ { - input: "src/index.js", + input: "src/index.ts", output: { file: "dist/span-panel-card.js", format: "iife", @@ -14,7 +15,7 @@ export default [ plugins, }, { - input: "src/panel/index.js", + input: "src/panel/index.ts", output: { file: "dist/span-panel.js", format: "iife", diff --git a/scripts/validate-i18n.mjs b/scripts/validate-i18n.mjs index e77cadb..3e4d4fd 100755 --- a/scripts/validate-i18n.mjs +++ b/scripts/validate-i18n.mjs @@ -1,6 +1,5 @@ #!/usr/bin/env node -/* eslint-disable no-console */ -/* global process */ +/* eslint-disable no-console, no-redeclare */ /** * Validate that every t() key used in source files exists in all translation * languages defined in src/i18n.js, and that English has no orphaned keys. @@ -12,7 +11,7 @@ import { readFileSync, readdirSync, statSync } from "fs"; import { join, relative } from "path"; const ROOT = new URL("..", import.meta.url).pathname.replace(/\/$/, ""); -const I18N_PATH = join(ROOT, "src", "i18n.js"); +const I18N_PATH = join(ROOT, "src", "i18n.ts"); // ── Extract translation keys from i18n.js ────────────────────────────────── @@ -59,7 +58,7 @@ function collectSourceKeys(dir, results = new Map()) { if (stat.isDirectory()) { if (entry === "node_modules" || entry === "dist") continue; collectSourceKeys(full, results); - } else if (entry.endsWith(".js") && full !== I18N_PATH) { + } else if ((entry.endsWith(".ts") || entry.endsWith(".js")) && full !== I18N_PATH) { const content = readFileSync(full, "utf8"); // Match t("key") and t('key') — both template and regular strings const re = /\bt\(\s*["']([^"']+)["']\s*\)/g; @@ -92,13 +91,13 @@ const languages = Object.keys(langKeys); const enKeys = langKeys.en; if (!enKeys || enKeys.size === 0) { - console.error("ERROR: No English translation keys found in src/i18n.js"); + console.error("ERROR: No English translation keys found in src/i18n.ts"); process.exit(1); } const srcDir = join(ROOT, "src"); const usedKeys = collectSourceKeys(srcDir); -let errors = []; +const errors = []; // 1. Every t() key must exist in English for (const [key, files] of usedKeys) { diff --git a/src/card/card-discovery.js b/src/card/card-discovery.js deleted file mode 100644 index 9b730f6..0000000 --- a/src/card/card-discovery.js +++ /dev/null @@ -1,162 +0,0 @@ -import { INTEGRATION_DOMAIN } from "../constants.js"; -import { t } from "../i18n.js"; - -// ── Primary discovery via custom WebSocket API ─────────────────────────────── - -export async function discoverTopology(hass, deviceId) { - if (!deviceId) { - throw new Error(t("card.device_not_found")); - } - const topology = await hass.callWS({ - type: `${INTEGRATION_DOMAIN}/panel_topology`, - device_id: deviceId, - }); - - const panelSize = topology.panel_size || panelSizeFromCircuits(topology.circuits); - if (!panelSize) { - throw new Error(t("card.topology_error")); - } - - const devices = await hass.callWS({ type: "config/device_registry/list" }); - const panelDevice = devices.find(d => d.id === deviceId) || null; - - return { topology, panelDevice, panelSize }; -} - -// ── Backward-compatible panel size derivation ──────────────────────────────── - -function panelSizeFromCircuits(circuits) { - let maxTab = 0; - for (const circuit of Object.values(circuits || {})) { - for (const tab of circuit.tabs || []) { - if (tab > maxTab) maxTab = tab; - } - } - return maxTab > 0 ? maxTab + (maxTab % 2) : 0; -} - -// ── Fallback discovery from entity registry ────────────────────────────────── - -export async function discoverEntitiesFallback(hass, deviceId) { - const [devices, entities] = await Promise.all([hass.callWS({ type: "config/device_registry/list" }), hass.callWS({ type: "config/entity_registry/list" })]); - - const panelDevice = devices.find(d => d.id === deviceId) || null; - if (!panelDevice) return { topology: null, panelDevice: null, panelSize: 0 }; - - const allEntities = entities.filter(e => e.device_id === deviceId); - const subDevices = devices.filter(d => d.via_device_id === deviceId); - const subDeviceIds = new Set(subDevices.map(d => d.id)); - const subEntities = entities.filter(e => subDeviceIds.has(e.device_id)); - - const circuits = {}; - const devName = panelDevice.name_by_user || panelDevice.name || ""; - - for (const ent of [...allEntities, ...subEntities]) { - const state = hass.states[ent.entity_id]; - if (!state || !state.attributes || !state.attributes.tabs) continue; - - const tabsAttr = state.attributes.tabs; - if (!tabsAttr || !tabsAttr.startsWith("tabs [")) continue; - const content = tabsAttr.slice(6, -1); - let tabs; - if (content.includes(":")) { - tabs = content.split(":").map(Number); - } else { - tabs = [Number(content)]; - } - if (!tabs.every(Number.isFinite)) continue; - - const uidParts = ent.unique_id.split("_"); - let circuitUuid = null; - for (let i = 2; i < uidParts.length - 1; i++) { - if (uidParts[i].length >= 16 && /^[a-f0-9]+$/i.test(uidParts[i])) { - circuitUuid = uidParts[i]; - break; - } - } - if (!circuitUuid) continue; - - let displayName = state.attributes.friendly_name || ent.entity_id; - for (const suffix of [" Power", " Consumed Energy", " Produced Energy"]) { - if (displayName.endsWith(suffix)) { - displayName = displayName.slice(0, -suffix.length); - break; - } - } - if (devName && displayName.startsWith(devName + " ")) { - displayName = displayName.slice(devName.length + 1); - } - - const base = ent.entity_id.replace(/^sensor\./, "").replace(/_power$/, ""); - - circuits[circuitUuid] = { - tabs, - name: displayName, - voltage: state.attributes.voltage || (tabs.length === 2 ? 240 : 120), - device_type: state.attributes.device_type || "circuit", - relay_state: state.attributes.relay_state || "UNKNOWN", - is_user_controllable: true, - breaker_rating_a: null, - entities: { - power: ent.entity_id, - switch: `switch.${base}_breaker`, - breaker_rating: `sensor.${base}_breaker_rating`, - }, - }; - } - - let serial = ""; - if (panelDevice.identifiers) { - for (const pair of panelDevice.identifiers) { - if (pair[0] === INTEGRATION_DOMAIN) serial = pair[1]; - } - } - - let panelSize = 0; - for (const ent of allEntities) { - const state = hass.states[ent.entity_id]; - if (state && state.attributes && state.attributes.panel_size) { - panelSize = state.attributes.panel_size; - break; - } - } - if (!panelSize) { - panelSize = panelSizeFromCircuits(circuits); - } - if (!panelSize) { - throw new Error(t("card.panel_size_error")); - } - - const subDeviceMap = {}; - for (const sub of subDevices) { - const subEnts = entities.filter(e => e.device_id === sub.id); - const isBess = (sub.model || "").toLowerCase().includes("battery") || (sub.identifiers || []).some(p => (p[1] || "").toLowerCase().includes("bess")); - const isEvse = (sub.model || "").toLowerCase().includes("drive") || (sub.identifiers || []).some(p => (p[1] || "").toLowerCase().includes("evse")); - - const entMap = {}; - for (const e of subEnts) { - entMap[e.entity_id] = { - domain: e.entity_id.split(".")[0], - original_name: hass.states[e.entity_id]?.attributes?.friendly_name || e.entity_id, - }; - } - - subDeviceMap[sub.id] = { - name: sub.name_by_user || sub.name || "", - type: isBess ? "bess" : isEvse ? "evse" : "unknown", - entities: entMap, - }; - } - - const topology = { - serial, - firmware: panelDevice.sw_version || "", - panel_size: panelSize, - device_id: deviceId, - device_name: panelDevice.name_by_user || panelDevice.name || t("header.default_name"), - circuits, - sub_devices: subDeviceMap, - }; - - return { topology, panelDevice, panelSize }; -} diff --git a/src/card/card-discovery.ts b/src/card/card-discovery.ts new file mode 100644 index 0000000..2251fe4 --- /dev/null +++ b/src/card/card-discovery.ts @@ -0,0 +1,220 @@ +import { INTEGRATION_DOMAIN } from "../constants.js"; +import { t } from "../i18n.js"; +import type { HomeAssistant, PanelTopology, PanelDevice, DiscoveryResult, Circuit, CircuitEntities } from "../types.js"; + +// ── HA registry response shapes (internal) ───────────────────────────────── + +interface DeviceRegistryEntry { + id: string; + name?: string; + name_by_user?: string; + config_entries?: string[]; + identifiers?: [string, string][]; + via_device_id?: string | null; + sw_version?: string; + model?: string; +} + +interface EntityRegistryEntry { + entity_id: string; + device_id?: string; + unique_id: string; + platform?: string; +} + +// ── Primary discovery via custom WebSocket API ─────────────────────────────── + +export async function discoverTopology(hass: HomeAssistant, deviceId: string | undefined): Promise { + if (!deviceId) { + throw new Error(t("card.device_not_found")); + } + const topology = await hass.callWS({ + type: `${INTEGRATION_DOMAIN}/panel_topology`, + device_id: deviceId, + }); + + const panelSize = topology.panel_size ?? panelSizeFromCircuits(topology.circuits); + if (!panelSize) { + throw new Error(t("card.topology_error")); + } + + const devices = await hass.callWS({ + type: "config/device_registry/list", + }); + const panelDevice = deviceToPanelDevice(devices.find(d => d.id === deviceId)); + + return { topology, panelDevice, panelSize }; +} + +// ── Backward-compatible panel size derivation ──────────────────────────────── + +function panelSizeFromCircuits(circuits: Record): number { + let maxTab = 0; + for (const circuit of Object.values(circuits)) { + if (!circuit) continue; + for (const tab of circuit.tabs) { + if (tab > maxTab) maxTab = tab; + } + } + return maxTab > 0 ? maxTab + (maxTab % 2) : 0; +} + +// ── Map device registry entry to PanelDevice ───────────────────────────────── + +function deviceToPanelDevice(entry: DeviceRegistryEntry | undefined): PanelDevice | null { + if (!entry) return null; + return { + id: entry.id, + name: entry.name, + name_by_user: entry.name_by_user, + config_entries: entry.config_entries, + identifiers: entry.identifiers, + via_device_id: entry.via_device_id, + sw_version: entry.sw_version, + model: entry.model, + }; +} + +// ── Fallback discovery from entity registry ────────────────────────────────── + +export async function discoverEntitiesFallback(hass: HomeAssistant, deviceId: string | undefined): Promise { + const [devices, entities] = await Promise.all([ + hass.callWS({ + type: "config/device_registry/list", + }), + hass.callWS({ + type: "config/entity_registry/list", + }), + ]); + + const panelDevice = deviceToPanelDevice(devices.find(d => d.id === deviceId)); + if (!panelDevice) return { topology: null, panelDevice: null, panelSize: 0 }; + + const allEntities = entities.filter(e => e.device_id === deviceId); + const subDevices = devices.filter(d => d.via_device_id === deviceId); + const subDeviceIds = new Set(subDevices.map(d => d.id)); + const subEntities = entities.filter(e => e.device_id !== undefined && subDeviceIds.has(e.device_id)); + + const circuits: Record = {}; + const devName = panelDevice.name_by_user ?? panelDevice.name ?? ""; + + for (const ent of [...allEntities, ...subEntities]) { + const state = hass.states[ent.entity_id]; + if (!state) continue; + const attrs = state.attributes; + const tabsAttr = attrs.tabs; + if (typeof tabsAttr !== "string" || !tabsAttr.startsWith("tabs [")) continue; + + const content = tabsAttr.slice(6, -1); + let tabs: number[]; + if (content.includes(":")) { + tabs = content.split(":").map(Number); + } else { + tabs = [Number(content)]; + } + if (!tabs.every(Number.isFinite)) continue; + + const uidParts = ent.unique_id.split("_"); + let circuitUuid: string | null = null; + for (let i = 2; i < uidParts.length - 1; i++) { + const part = uidParts[i]; + if (part !== undefined && part.length >= 16 && /^[a-f0-9]+$/i.test(part)) { + circuitUuid = part; + break; + } + } + if (!circuitUuid) continue; + + let displayName = (typeof attrs.friendly_name === "string" ? attrs.friendly_name : undefined) ?? ent.entity_id; + for (const suffix of [" Power", " Consumed Energy", " Produced Energy"]) { + if (displayName.endsWith(suffix)) { + displayName = displayName.slice(0, -suffix.length); + break; + } + } + if (devName && displayName.startsWith(devName + " ")) { + displayName = displayName.slice(devName.length + 1); + } + + const base = ent.entity_id.replace(/^sensor\./, "").replace(/_power$/, ""); + + const voltage = typeof attrs.voltage === "number" ? attrs.voltage : tabs.length === 2 ? 240 : 120; + + const circuitEntities: CircuitEntities = { + power: ent.entity_id, + switch: `switch.${base}_breaker`, + breaker_rating: `sensor.${base}_breaker_rating`, + }; + + circuits[circuitUuid] = { + tabs, + name: displayName, + voltage, + device_type: typeof attrs.device_type === "string" ? attrs.device_type : "circuit", + relay_state: typeof attrs.relay_state === "string" ? attrs.relay_state : "UNKNOWN", + is_user_controllable: true, + breaker_rating_a: null, + entities: circuitEntities, + }; + } + + let serial = ""; + if (panelDevice.identifiers) { + for (const pair of panelDevice.identifiers) { + if (pair[0] === INTEGRATION_DOMAIN) serial = pair[1]; + } + } + + let panelSize = 0; + for (const ent of allEntities) { + const state = hass.states[ent.entity_id]; + if (state && typeof state.attributes.panel_size === "number") { + panelSize = state.attributes.panel_size; + break; + } + } + if (!panelSize) { + panelSize = panelSizeFromCircuits(circuits); + } + if (!panelSize) { + throw new Error(t("card.panel_size_error")); + } + + const subDeviceMap: Record }> = {}; + + for (const sub of subDevices) { + const subEnts = entities.filter(e => e.device_id === sub.id); + const modelLower = (sub.model ?? "").toLowerCase(); + const isBess = modelLower.includes("battery") || (sub.identifiers ?? []).some(p => p[1].toLowerCase().includes("bess")); + const isEvse = modelLower.includes("drive") || (sub.identifiers ?? []).some(p => p[1].toLowerCase().includes("evse")); + + const entMap: Record = {}; + for (const e of subEnts) { + const domainPart = e.entity_id.split(".")[0]; + const subState = hass.states[e.entity_id]; + const friendlyName = subState?.attributes?.friendly_name; + entMap[e.entity_id] = { + domain: domainPart ?? "", + original_name: typeof friendlyName === "string" ? friendlyName : e.entity_id, + }; + } + + subDeviceMap[sub.id] = { + name: sub.name_by_user ?? sub.name ?? "", + type: isBess ? "bess" : isEvse ? "evse" : "unknown", + entities: entMap, + }; + } + + const topology: PanelTopology = { + serial, + firmware: panelDevice.sw_version ?? "", + panel_size: panelSize, + device_id: deviceId, + device_name: panelDevice.name_by_user ?? panelDevice.name ?? t("header.default_name"), + circuits, + sub_devices: subDeviceMap, + }; + + return { topology, panelDevice, panelSize }; +} diff --git a/src/card/card-styles.js b/src/card/card-styles.ts similarity index 99% rename from src/card/card-styles.js rename to src/card/card-styles.ts index 99ddba1..225c09c 100644 --- a/src/card/card-styles.js +++ b/src/card/card-styles.ts @@ -1,4 +1,4 @@ -export const CARD_STYLES = ` +export const CARD_STYLES: string = ` :host { --span-accent: var(--primary-color, #4dd9af); } diff --git a/src/card/span-panel-card.js b/src/card/span-panel-card.js deleted file mode 100644 index ef0e223..0000000 --- a/src/card/span-panel-card.js +++ /dev/null @@ -1,621 +0,0 @@ -import { DEFAULT_CHART_METRIC, DEFAULT_GRAPH_HORIZON, GRAPH_HORIZONS, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; -import { setLanguage, t } from "../i18n.js"; -import { escapeHtml } from "../helpers/sanitize.js"; -import { getHistoryDurationMs, getHorizonDurationMs, getMaxHistoryPoints, recordSample } from "../helpers/history.js"; -import { getCircuitChartEntity } from "../helpers/chart.js"; -import { GraphSettingsCache } from "../core/graph-settings.js"; -import { buildHeaderHTML } from "../core/header-renderer.js"; -import { buildGridHTML } from "../core/grid-renderer.js"; -import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; -import { loadHistory, collectSubDeviceEntityIds } from "../core/history-loader.js"; -import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; -import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; -import { CARD_STYLES } from "./card-styles.js"; -import "../core/side-panel.js"; -import { MonitoringStatusCache, buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; - -export class SpanPanelCard extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: "open" }); - this._hass = null; - this._config = {}; - this._discovered = false; - this._discovering = false; - - this._topology = null; - this._panelDevice = null; - this._panelSize = 0; - - this._powerHistory = new Map(); - this._historyLoaded = false; - - this._updateInterval = null; - this._recorderRefreshInterval = null; - this._rendered = false; - - this._handleToggleClick = this._onToggleClick.bind(this); - this._handleUnitToggle = this._onUnitToggle.bind(this); - this._handleGearClick = this._onGearClick.bind(this); - this._handleGraphSettingsChanged = this._onGraphSettingsChanged.bind(this); - this._monitoringCache = new MonitoringStatusCache(); - this._graphSettingsCache = new GraphSettingsCache(); - this._horizonMap = new Map(); - this._subDeviceHorizonMap = new Map(); - this._resizeObserver = null; - this._lastCardWidth = 0; - this._resizeDebounce = null; - } - - connectedCallback() { - this._updateInterval = setInterval(() => { - if (this._discovered && this._hass) { - this._updateData(); - } - }, LIVE_SAMPLE_INTERVAL_MS); - - this._recorderRefreshInterval = setInterval(async () => { - if (!this._discovered || !this._hass || !this._topology) return; - const nonRealtimeMap = new Map(); - for (const [uuid, horizon] of this._horizonMap) { - if (!GRAPH_HORIZONS[horizon]?.useRealtime) { - nonRealtimeMap.set(uuid, horizon); - } - } - if (nonRealtimeMap.size === 0) return; - // Load into a temporary map so charts keep showing stale data - // until fresh data is ready (avoids blank-chart flash). - const freshHistory = new Map(); - try { - await loadHistory(this._hass, this._topology, this._config, freshHistory, nonRealtimeMap); - // Atomically replace only the non-realtime entries - for (const uuid of nonRealtimeMap.keys()) { - const data = freshHistory.get(uuid); - if (data) { - this._powerHistory.set(uuid, data); - } else { - this._powerHistory.delete(uuid); - } - } - this._updateDOM(); - } catch { - // Will refresh on next interval - } - }, 30000); - - // Re-render when card is reconnected after navigation - if (this._discovered && this._hass && this._rendered) { - this._updateDOM(); - } - - this._onVisibilityChange = () => { - if (document.visibilityState === "visible" && this._discovered && this._hass) { - this._updateDOM(); - } - }; - document.addEventListener("visibilitychange", this._onVisibilityChange); - } - - disconnectedCallback() { - if (this._updateInterval) { - clearInterval(this._updateInterval); - this._updateInterval = null; - } - if (this._recorderRefreshInterval) { - clearInterval(this._recorderRefreshInterval); - this._recorderRefreshInterval = null; - } - if (this._onVisibilityChange) { - document.removeEventListener("visibilitychange", this._onVisibilityChange); - this._onVisibilityChange = null; - } - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - this._resizeObserver = null; - } - if (this._resizeDebounce) { - clearTimeout(this._resizeDebounce); - this._resizeDebounce = null; - } - } - - setConfig(config) { - this._config = config; - this._discovered = false; - this._rendered = false; - this._historyLoaded = false; - this._powerHistory.clear(); - this._horizonMap.clear(); - this._subDeviceHorizonMap.clear(); - this._monitoringCache.clear(); - this._graphSettingsCache.clear(); - } - - get _durationMs() { - return getHistoryDurationMs(this._config); - } - - get _configEntryId() { - return this._panelDevice?.config_entries?.[0] || null; - } - - set hass(hass) { - this._hass = hass; - setLanguage(hass?.language); - if (!this._config.device_id) { - this.shadowRoot.innerHTML = ` - -
- ${t("card.no_device")} -
-
- `; - return; - } - if (!this._discovered && !this._discovering) { - this._discovering = true; - this._discoverTopology().then(() => { - this._discovered = true; - this._discovering = false; - this._render(); - this._loadHistory(); - this._monitoringCache.fetch(hass, this._configEntryId).then(() => { - if (this._rendered) this._updateDOM(); - }); - }); - return; - } - if (this._discovered) { - this._updateData(); - } - } - - getCardSize() { - return Math.ceil(this._panelSize / 2) + 3; - } - - static getConfigElement() { - return document.createElement("span-panel-card-editor"); - } - - static getStubConfig() { - return { - device_id: "", - history_days: 0, - history_hours: 0, - history_minutes: 5, - chart_metric: DEFAULT_CHART_METRIC, - show_panel: true, - show_battery: true, - show_evse: true, - }; - } - - // ── Discovery ────────────────────────────────────────────────────────────── - - async _discoverTopology() { - if (!this._hass) return; - try { - const result = await discoverTopology(this._hass, this._config.device_id); - this._topology = result.topology; - this._panelDevice = result.panelDevice; - this._panelSize = result.panelSize; - } catch (err) { - console.error("SPAN Panel: topology fetch failed, falling back to entity discovery", err); - try { - const result = await discoverEntitiesFallback(this._hass, this._config.device_id); - this._topology = result.topology; - this._panelDevice = result.panelDevice; - this._panelSize = result.panelSize; - } catch (fallbackErr) { - console.error("SPAN Panel: fallback discovery also failed", fallbackErr); - this._discoveryError = fallbackErr.message; - } - } - } - - // ── History from HA recorder ─────────────────────────────────────────────── - - async _loadHistory() { - if (this._historyLoaded || !this._topology || !this._hass) return; - this._historyLoaded = true; - - // Fetch graph settings and build horizon map - try { - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - const settings = this._graphSettingsCache.settings; - if (settings && this._topology?.circuits) { - for (const uuid of Object.keys(this._topology.circuits)) { - const override = settings.circuits?.[uuid]; - const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; - this._horizonMap.set(uuid, horizon); - } - } - // Build sub-device horizon map - if (settings && this._topology?.sub_devices) { - for (const devId of Object.keys(this._topology.sub_devices)) { - const override = settings.sub_devices?.[devId]; - const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; - this._subDeviceHorizonMap.set(devId, horizon); - } - } - } catch { - // Graph settings unavailable — use defaults - } - - try { - await loadHistory(this._hass, this._topology, this._config, this._powerHistory, this._horizonMap, this._subDeviceHorizonMap); - this._updateDOM(); - } catch (err) { - console.warn("SPAN Panel: history fetch failed, charts will populate live", err); - } - } - - // ── Record live power samples ────────────────────────────────────────────── - - _recordPowerHistory() { - if (!this._topology || !this._hass) return; - const now = Date.now(); - - for (const [uuid, circuit] of Object.entries(this._topology.circuits)) { - const horizon = this._horizonMap?.get(uuid) || DEFAULT_GRAPH_HORIZON; - if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; - - const entityId = getCircuitChartEntity(circuit, this._config); - if (!entityId) continue; - const state = this._hass.states[entityId]; - const rawValue = state ? parseFloat(state.state) || 0 : 0; - - const durationMs = getHorizonDurationMs(horizon); - const maxPoints = getMaxHistoryPoints(durationMs); - const cutoff = now - durationMs; - recordSample(this._powerHistory, uuid, rawValue, now, cutoff, maxPoints); - } - - // Sub-devices use per-device horizon when available - for (const { entityId, key, devId } of collectSubDeviceEntityIds(this._topology)) { - const horizon = this._subDeviceHorizonMap?.get(devId) || DEFAULT_GRAPH_HORIZON; - if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; - - const state = this._hass.states[entityId]; - const rawValue = state ? parseFloat(state.state) || 0 : 0; - const durationMs = getHorizonDurationMs(horizon); - const maxPoints = getMaxHistoryPoints(durationMs); - const cutoff = now - durationMs; - recordSample(this._powerHistory, key, rawValue, now, cutoff, maxPoints); - } - } - - // ── Data update ──────────────────────────────────────────────────────────── - - _updateData() { - this._recordPowerHistory(); - this._updateDOM(); - } - - // ── DOM updates (incremental) ────────────────────────────────────────────── - - _updateDOM() { - updateCircuitDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(this.shadowRoot, this._hass, this._topology, this._config, this._powerHistory, this._subDeviceHorizonMap); - } - - // ── Unit toggle (A/W) click handler ─────────────────────────────────────── - - async _onUnitToggle(event) { - const btn = event.target.closest(".unit-btn"); - if (!btn) return; - const unit = btn.dataset.unit; - if (!unit || unit === (this._config.chart_metric || "power")) return; - this._config = { ...this._config, chart_metric: unit }; - this.dispatchEvent( - new CustomEvent("config-changed", { - detail: { config: this._config }, - bubbles: true, - composed: true, - }) - ); - this._powerHistory.clear(); - this._historyLoaded = false; - this._rendered = false; - this._render(); - await this._loadHistory(); - this._updateDOM(); - } - - // ── Slide-to-confirm safety switch ───────────────────────────────────────── - - _bindSlideConfirm(slideEl, parent) { - const knob = slideEl.querySelector(".slide-confirm-knob"); - const textEl = slideEl.querySelector(".slide-confirm-text"); - if (!knob) return; - const THRESHOLD = 0.9; - let dragging = false; - let startX = 0; - let maxX = 0; - - const begin = clientX => { - if (slideEl.classList.contains("confirmed")) return; - dragging = true; - startX = clientX - knob.offsetLeft; - maxX = slideEl.offsetWidth - knob.offsetWidth - 4; - knob.classList.remove("snapping"); - }; - const move = clientX => { - if (!dragging) return; - const x = Math.max(2, Math.min(clientX - startX, maxX)); - knob.style.left = x + "px"; - }; - const end = () => { - if (!dragging) return; - dragging = false; - const pos = (knob.offsetLeft - 2) / maxX; - if (pos >= THRESHOLD) { - knob.style.left = maxX + "px"; - slideEl.classList.add("confirmed"); - knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock-open"); - textEl.textContent = slideEl.dataset.textOn; - if (parent) parent.classList.remove("switches-disabled"); - } else { - knob.classList.add("snapping"); - knob.style.left = "2px"; - } - }; - - knob.addEventListener("mousedown", e => { - e.preventDefault(); - begin(e.clientX); - }); - slideEl.addEventListener("mousemove", e => move(e.clientX)); - slideEl.addEventListener("mouseup", end); - slideEl.addEventListener("mouseleave", end); - knob.addEventListener( - "touchstart", - e => { - e.preventDefault(); - begin(e.touches[0].clientX); - }, - { passive: false } - ); - slideEl.addEventListener("touchmove", e => move(e.touches[0].clientX), { passive: true }); - slideEl.addEventListener("touchend", end); - slideEl.addEventListener("touchcancel", end); - - // Click the confirmed slider to re-lock - slideEl.addEventListener("click", () => { - if (!slideEl.classList.contains("confirmed")) return; - slideEl.classList.remove("confirmed"); - knob.classList.add("snapping"); - knob.style.left = "2px"; - knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock"); - textEl.textContent = slideEl.dataset.textOff; - if (parent) parent.classList.add("switches-disabled"); - }); - } - - // ── Toggle click handler ─────────────────────────────────────────────────── - - _onToggleClick(ev) { - const pill = ev.target.closest(".toggle-pill"); - if (!pill) return; - const cb = this.shadowRoot.querySelector(".slide-confirm"); - if (!cb || !cb.classList.contains("confirmed")) return; - ev.stopPropagation(); - ev.preventDefault(); - const slot = pill.closest("[data-uuid]"); - if (!slot || !this._topology || !this._hass) return; - const uuid = slot.dataset.uuid; - const circuit = this._topology.circuits[uuid]; - if (!circuit) return; - const switchEntity = circuit.entities?.switch; - if (!switchEntity) return; - const switchState = this._hass.states[switchEntity]; - if (!switchState) { - console.warn("SPAN Panel: switch entity not found:", switchEntity); - return; - } - const service = switchState.state === "on" ? "turn_off" : "turn_on"; - this._hass.callService("switch", service, {}, { entity_id: switchEntity }).catch(err => { - console.error("SPAN Panel: switch service call failed:", err); - }); - } - - // ── Gear click handler ──────────────────────────────────────────────────── - - async _onGearClick(event) { - const gearBtn = event.target.closest(".gear-icon"); - if (!gearBtn) return; - - const sidePanel = this.shadowRoot.querySelector("span-side-panel"); - if (!sidePanel) return; - sidePanel.hass = this._hass; - - if (gearBtn.classList.contains("panel-gear")) { - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - sidePanel.open({ - panelMode: true, - topology: this._topology, - graphSettings: this._graphSettingsCache.settings, - }); - return; - } - - const uuid = gearBtn.dataset.uuid; - if (uuid && this._topology) { - const circuit = this._topology.circuits[uuid]; - if (circuit) { - const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; - - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - const graphSettings = this._graphSettingsCache.settings; - const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - const graphHorizonInfo = graphSettings?.circuits?.[uuid] - ? { ...graphSettings.circuits[uuid], globalHorizon } - : { horizon: globalHorizon, has_override: false, globalHorizon }; - - sidePanel.open({ - ...circuit, - uuid, - monitoringInfo, - graphHorizonInfo, - }); - return; - } - } - - const subDevId = gearBtn.dataset.subdevId; - if (subDevId && this._topology?.sub_devices?.[subDevId]) { - const sub = this._topology.sub_devices[subDevId]; - - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - const graphSettings = this._graphSettingsCache.settings; - const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - const graphHorizonInfo = graphSettings?.sub_devices?.[subDevId] - ? { ...graphSettings.sub_devices[subDevId], globalHorizon } - : { horizon: globalHorizon, has_override: false, globalHorizon }; - - sidePanel.open({ - subDeviceMode: true, - subDeviceId: subDevId, - name: sub.name || subDevId, - deviceType: sub.type || "", - graphHorizonInfo, - }); - } - } - - // ── Graph settings changed handler ──────────────────────────────────────── - - async _onGraphSettingsChanged() { - this._graphSettingsCache.invalidate(); - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - - // Rebuild horizon map - const settings = this._graphSettingsCache.settings; - if (settings && this._topology?.circuits) { - for (const uuid of Object.keys(this._topology.circuits)) { - const override = settings.circuits?.[uuid]; - const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; - this._horizonMap.set(uuid, horizon); - } - } - - // Rebuild sub-device horizon map - if (settings && this._topology?.sub_devices) { - for (const devId of Object.keys(this._topology.sub_devices)) { - const override = settings.sub_devices?.[devId]; - const horizon = override?.has_override ? override.horizon : settings.global_horizon || DEFAULT_GRAPH_HORIZON; - this._subDeviceHorizonMap.set(devId, horizon); - } - } - - // Reload all history with new horizons - this._powerHistory.clear(); - this._historyLoaded = false; - await this._loadHistory(); - } - - // ── Resize handling ──────────────────────────────────────────────────────── - - _invalidateCharts() { - for (const container of this.shadowRoot.querySelectorAll(".chart-container")) { - const chart = container.querySelector("ha-chart-base"); - if (chart) chart.remove(); - } - } - - _setupResizeObserver() { - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - } - const card = this.shadowRoot.querySelector("ha-card"); - if (!card) return; - this._lastCardWidth = card.clientWidth; - this._resizeObserver = new ResizeObserver(entries => { - const entry = entries[0]; - if (!entry) return; - const newWidth = entry.contentRect.width; - if (Math.abs(newWidth - this._lastCardWidth) < 5) return; - this._lastCardWidth = newWidth; - if (this._resizeDebounce) clearTimeout(this._resizeDebounce); - this._resizeDebounce = setTimeout(() => { - this._invalidateCharts(); - this._updateDOM(); - }, 150); - }); - this._resizeObserver.observe(card); - } - - // ── Full render ──────────────────────────────────────────────────────────── - - _render() { - const hass = this._hass; - if (!hass || !this._topology || !this._panelSize) { - const msg = this._discoveryError || (!this._topology ? t("card.device_not_found") : t("card.loading")); - this.shadowRoot.innerHTML = ` - -
- ${escapeHtml(msg)} -
-
- `; - return; - } - - const topo = this._topology; - const totalRows = Math.ceil(this._panelSize / 2); - const durationMs = this._durationMs; - - const headerHTML = buildHeaderHTML(topo, this._config); - const monitoringStatus = this._monitoringCache.status; - const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); - const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, this._config, monitoringStatus); - const subDevHTML = buildSubDevicesHTML(topo, hass, this._config, durationMs); - - // Remove previous listeners before replacing DOM - this.shadowRoot.removeEventListener("click", this._handleToggleClick); - this.shadowRoot.removeEventListener("click", this._handleUnitToggle); - this.shadowRoot.removeEventListener("click", this._handleGearClick); - this.shadowRoot.removeEventListener("graph-settings-changed", this._handleGraphSettingsChanged); - - this.shadowRoot.innerHTML = ` - - - ${headerHTML} - ${monitoringSummaryHTML} - ${subDevHTML ? `
${subDevHTML}
` : ""} - ${ - this._config.show_panel !== false - ? ` -
- ${gridHTML} -
- ` - : "" - } -
- - `; - - // Attach delegated click listeners - this.shadowRoot.addEventListener("click", this._handleToggleClick); - this.shadowRoot.addEventListener("click", this._handleUnitToggle); - this.shadowRoot.addEventListener("click", this._handleGearClick); - this.shadowRoot.addEventListener("graph-settings-changed", this._handleGraphSettingsChanged); - - const slideEl = this.shadowRoot.querySelector(".slide-confirm"); - if (slideEl) { - this._bindSlideConfirm(slideEl, this.shadowRoot.querySelector("ha-card")); - const card = this.shadowRoot.querySelector("ha-card"); - if (card) card.classList.add("switches-disabled"); - } - - const sidePanel = this.shadowRoot.querySelector("span-side-panel"); - if (sidePanel) sidePanel.hass = hass; - - this._rendered = true; - this._recordPowerHistory(); - this._updateDOM(); - this._setupResizeObserver(); - } -} diff --git a/src/card/span-panel-card.ts b/src/card/span-panel-card.ts new file mode 100644 index 0000000..29aae55 --- /dev/null +++ b/src/card/span-panel-card.ts @@ -0,0 +1,267 @@ +import { DEFAULT_CHART_METRIC } from "../constants.js"; +import { setLanguage, t } from "../i18n.js"; +import { escapeHtml } from "../helpers/sanitize.js"; +import { buildHeaderHTML } from "../core/header-renderer.js"; +import { buildGridHTML } from "../core/grid-renderer.js"; +import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; +import { buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; +import { DashboardController } from "../core/dashboard-controller.js"; +import { discoverTopology, discoverEntitiesFallback } from "./card-discovery.js"; +import { CARD_STYLES } from "./card-styles.js"; +import "../core/side-panel.js"; +import type { HomeAssistant, PanelTopology, PanelDevice, CardConfig } from "../types.js"; + +interface SpanSidePanelElement extends HTMLElement { + hass: HomeAssistant; +} + +export class SpanPanelCard extends HTMLElement { + private _hass: HomeAssistant | null = null; + private _config: CardConfig = {}; + private _discovered = false; + private _discovering = false; + private _discoveryError: string | null = null; + + private _topology: PanelTopology | null = null; + private _panelDevice: PanelDevice | null = null; + private _panelSize = 0; + + private _historyLoaded = false; + private _rendered = false; + + private readonly _ctrl = new DashboardController(); + + private readonly _handleToggleClick: (ev: Event) => void; + private readonly _handleUnitToggle: (ev: Event) => void; + private readonly _handleGearClick: (ev: Event) => void; + private readonly _handleGraphSettingsChanged: () => void; + private _onVisibilityChange: (() => void) | null = null; + + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this._handleToggleClick = (ev: Event) => this._ctrl.onToggleClick(ev, this.shadowRoot!); + this._handleUnitToggle = this._onUnitToggle.bind(this); + this._handleGearClick = (ev: Event) => this._ctrl.onGearClick(ev, this.shadowRoot!); + this._handleGraphSettingsChanged = () => this._ctrl.onGraphSettingsChanged(this.shadowRoot!); + } + + connectedCallback(): void { + this._ctrl.startIntervals(this.shadowRoot!); + + if (this._discovered && this._hass && this._rendered) { + this._ctrl.updateDOM(this.shadowRoot!); + } + + this._onVisibilityChange = () => { + if (document.visibilityState === "visible" && this._discovered && this._hass) { + this._ctrl.updateDOM(this.shadowRoot!); + } + }; + document.addEventListener("visibilitychange", this._onVisibilityChange); + } + + disconnectedCallback(): void { + this._ctrl.stopIntervals(); + if (this._onVisibilityChange) { + document.removeEventListener("visibilitychange", this._onVisibilityChange); + this._onVisibilityChange = null; + } + } + + setConfig(config: CardConfig): void { + this._config = config; + this._discovered = false; + this._rendered = false; + this._historyLoaded = false; + this._discoveryError = null; + this._ctrl.reset(); + this._ctrl.setConfig(config); + } + + private get _configEntryId(): string | null { + return this._panelDevice?.config_entries?.[0] ?? null; + } + + set hass(hass: HomeAssistant) { + this._hass = hass; + this._ctrl.hass = hass; + setLanguage(hass?.language); + if (!this._config.device_id) { + this.shadowRoot!.innerHTML = ` + +
+ ${t("card.no_device")} +
+
+ `; + return; + } + if (!this._discovered && !this._discovering) { + this._discovering = true; + this._discoverTopology().then(() => { + this._discovered = true; + this._discovering = false; + this._ctrl.init(this._topology, this._config, this._hass, this._configEntryId); + this._render(); + this._loadHistory(); + this._ctrl.monitoringCache.fetch(hass, this._configEntryId).then(() => { + if (this._rendered) this._ctrl.updateDOM(this.shadowRoot!); + }); + }); + return; + } + if (this._discovered) { + this._ctrl.recordSamples(); + this._ctrl.updateDOM(this.shadowRoot!); + } + } + + getCardSize(): number { + return Math.ceil(this._panelSize / 2) + 3; + } + + static getConfigElement(): HTMLElement { + return document.createElement("span-panel-card-editor"); + } + + static getStubConfig(): CardConfig { + return { + device_id: "", + history_days: 0, + history_hours: 0, + history_minutes: 5, + chart_metric: DEFAULT_CHART_METRIC, + show_panel: true, + show_battery: true, + show_evse: true, + }; + } + + private async _discoverTopology(): Promise { + if (!this._hass) return; + try { + const result = await discoverTopology(this._hass, this._config.device_id); + this._topology = result.topology; + this._panelDevice = result.panelDevice; + this._panelSize = result.panelSize; + } catch (err) { + console.error("SPAN Panel: topology fetch failed, falling back to entity discovery", err); + try { + const result = await discoverEntitiesFallback(this._hass, this._config.device_id); + this._topology = result.topology; + this._panelDevice = result.panelDevice; + this._panelSize = result.panelSize; + } catch (fallbackErr) { + console.error("SPAN Panel: fallback discovery also failed", fallbackErr); + this._discoveryError = (fallbackErr as Error).message; + } + } + } + + private async _loadHistory(): Promise { + if (this._historyLoaded || !this._topology || !this._hass) return; + this._historyLoaded = true; + + await this._ctrl.fetchAndBuildHorizonMaps(); + + try { + await this._ctrl.loadHistory(); + this._ctrl.updateDOM(this.shadowRoot!); + } catch (err) { + console.warn("SPAN Panel: history fetch failed, charts will populate live", err); + } + } + + private async _onUnitToggle(event: Event): Promise { + const target = event.target as HTMLElement | null; + const btn = target?.closest(".unit-btn") as HTMLElement | null; + if (!btn) return; + const unit = btn.dataset.unit; + if (!unit || unit === (this._config.chart_metric ?? "power")) return; + this._config = { ...this._config, chart_metric: unit }; + this._ctrl.setConfig(this._config); + this.dispatchEvent( + new CustomEvent("config-changed", { + detail: { config: this._config }, + bubbles: true, + composed: true, + }) + ); + this._ctrl.powerHistory.clear(); + this._historyLoaded = false; + this._rendered = false; + this._render(); + await this._loadHistory(); + this._ctrl.updateDOM(this.shadowRoot!); + } + + private _render(): void { + const hass = this._hass; + if (!hass || !this._topology || !this._panelSize) { + const msg = this._discoveryError ?? (!this._topology ? t("card.device_not_found") : t("card.loading")); + this.shadowRoot!.innerHTML = ` + +
+ ${escapeHtml(msg)} +
+
+ `; + return; + } + + const topo = this._topology; + const totalRows = Math.ceil(this._panelSize / 2); + const headerHTML = buildHeaderHTML(topo, this._config); + const monitoringStatus = this._ctrl.monitoringCache.status; + const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); + const gridHTML = buildGridHTML(topo, totalRows, hass, this._config, monitoringStatus); + const subDevHTML = buildSubDevicesHTML(topo, hass, this._config); + + const sr = this.shadowRoot!; + + sr.removeEventListener("click", this._handleToggleClick); + sr.removeEventListener("click", this._handleUnitToggle); + sr.removeEventListener("click", this._handleGearClick); + sr.removeEventListener("graph-settings-changed", this._handleGraphSettingsChanged); + + sr.innerHTML = ` + + + ${headerHTML} + ${monitoringSummaryHTML} + ${subDevHTML ? `
${subDevHTML}
` : ""} + ${ + this._config.show_panel !== false + ? ` +
+ ${gridHTML} +
+ ` + : "" + } +
+ + `; + + sr.addEventListener("click", this._handleToggleClick); + sr.addEventListener("click", this._handleUnitToggle); + sr.addEventListener("click", this._handleGearClick); + sr.addEventListener("graph-settings-changed", this._handleGraphSettingsChanged); + + const slideEl = sr.querySelector(".slide-confirm"); + if (slideEl) { + this._ctrl.bindSlideConfirm(slideEl, sr.querySelector("ha-card")); + const card = sr.querySelector("ha-card"); + if (card) card.classList.add("switches-disabled"); + } + + const sidePanel = sr.querySelector("span-side-panel") as SpanSidePanelElement | null; + if (sidePanel) sidePanel.hass = hass; + + this._rendered = true; + this._ctrl.recordSamples(); + this._ctrl.updateDOM(sr); + this._ctrl.setupResizeObserver(sr, sr.querySelector("ha-card")); + } +} diff --git a/src/chart/chart-options.js b/src/chart/chart-options.js deleted file mode 100644 index 8ae1a82..0000000 --- a/src/chart/chart-options.js +++ /dev/null @@ -1,124 +0,0 @@ -import { CHART_METRICS, DEFAULT_CHART_METRIC } from "../constants.js"; - -export function buildChartOptions(history, durationMs, metric, isProducer, breakerRatingA) { - if (!metric) metric = CHART_METRICS[DEFAULT_CHART_METRIC]; - const accentRgb = isProducer ? "140, 160, 220" : "77, 217, 175"; - const accentColor = `rgb(${accentRgb})`; - const now = Date.now(); - const startTime = now - durationMs; - - const hasFixedRange = metric.fixedMin !== undefined && metric.fixedMax !== undefined; - const unit = metric.unit(0); - - const data = (history || []).filter(p => p.time >= startTime).map(p => [p.time, Math.abs(p.value)]); - - const series = [ - { - type: "line", - data, - showSymbol: false, - smooth: false, - lineStyle: { width: 1.5, color: accentColor }, - areaStyle: { - color: { - type: "linear", - x: 0, - y: 0, - x2: 0, - y2: 1, - colorStops: [ - { offset: 0, color: `rgba(${accentRgb}, 0.35)` }, - { offset: 1, color: `rgba(${accentRgb}, 0.02)` }, - ], - }, - }, - itemStyle: { color: accentColor }, - }, - ]; - - // Determine the max data value to ensure a meaningful Y-axis range - const dataMax = data.length > 0 ? Math.max(...data.map(d => d[1])) : 0; - const useDecimalAxis = dataMax < 10; - - const yAxis = { - type: "value", - splitNumber: 4, - axisLabel: { - fontSize: 10, - formatter: useDecimalAxis ? v => (v === 0 ? "0" : v.toFixed(1)) : v => metric.format(v), - }, - splitLine: { lineStyle: { opacity: 0.15 } }, - }; - if (hasFixedRange) { - yAxis.min = metric.fixedMin; - yAxis.max = metric.fixedMax; - } else if (dataMax < 1) { - yAxis.min = 0; - yAxis.max = 1; - } - - // When displaying current with a known breaker rating, fix Y-axis to 125% - // of the rating and draw a red limit line at 100% (NEC reference). - if (breakerRatingA && metric.entityRole === "current") { - yAxis.min = 0; - yAxis.max = Math.ceil(breakerRatingA * 1.25); - - // 80% NEC continuous load limit (yellow dashed) - series.push({ - type: "line", - data: [ - [startTime, breakerRatingA * 0.8], - [now, breakerRatingA * 0.8], - ], - showSymbol: false, - lineStyle: { width: 1, color: "rgba(255, 200, 40, 0.6)", type: "dashed" }, - itemStyle: { color: "transparent" }, - tooltip: { show: false }, - }); - // 100% breaker rating (red solid) - series.push({ - type: "line", - data: [ - [startTime, breakerRatingA], - [now, breakerRatingA], - ], - showSymbol: false, - lineStyle: { width: 1.5, color: "rgba(255, 60, 60, 0.7)", type: "solid" }, - itemStyle: { color: "transparent" }, - tooltip: { show: false }, - }); - } - - const options = { - xAxis: { - type: "time", - min: startTime, - max: now, - axisLabel: { fontSize: 10 }, - splitLine: { show: false }, - }, - yAxis, - grid: { top: 8, right: 4, bottom: 0, left: 0, containLabel: true }, - tooltip: { - trigger: "axis", - axisPointer: { type: "line", lineStyle: { type: "dashed" } }, - formatter: params => { - if (!params || !params.length) return ""; - const p = params[0]; - const date = new Date(p.value[0]); - const timeStr = date.toLocaleString(undefined, { - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - const val = parseFloat(p.value[1].toFixed(2)); - return `
${timeStr}
${val} ${unit}
`; - }, - }, - animation: false, - }; - - return { options, series }; -} diff --git a/src/chart/chart-options.ts b/src/chart/chart-options.ts new file mode 100644 index 0000000..329f7dc --- /dev/null +++ b/src/chart/chart-options.ts @@ -0,0 +1,195 @@ +import { CHART_METRICS, DEFAULT_CHART_METRIC, NEC_CONTINUOUS_LOAD_FACTOR, NEC_TRIP_RATING_FACTOR } from "../constants.js"; +import type { HistoryPoint, ChartMetricDef } from "../types.js"; + +type DataPair = [number, number]; + +interface SeriesDef { + type: "line"; + data: DataPair[]; + showSymbol: boolean; + smooth?: boolean; + lineStyle: { width: number; color: string; type?: string }; + areaStyle?: { + color: { + type: "linear"; + x: number; + y: number; + x2: number; + y2: number; + colorStops: { offset: number; color: string }[]; + }; + }; + itemStyle: { color: string }; + tooltip?: { show: boolean }; +} + +interface YAxisDef { + type: "value"; + splitNumber: number; + axisLabel: { + fontSize: number; + formatter: (v: number) => string; + }; + splitLine: { lineStyle: { opacity: number } }; + min?: number; + max?: number; +} + +interface ChartOptionsDef { + xAxis: { + type: "time"; + min: number; + max: number; + axisLabel: { fontSize: number }; + splitLine: { show: boolean }; + }; + yAxis: YAxisDef; + grid: { top: number; right: number; bottom: number; left: number; containLabel: boolean }; + tooltip: { + trigger: "axis"; + axisPointer: { type: "line"; lineStyle: { type: "dashed" } }; + formatter: (params: { value: [number, number] }[]) => string; + }; + animation: boolean; +} + +export interface BuildChartResult { + options: ChartOptionsDef; + series: SeriesDef[]; +} + +function safeMax(data: DataPair[]): number { + let max = 0; + for (const pair of data) { + if (pair[1] > max) { + max = pair[1]; + } + } + return max; +} + +export function buildChartOptions( + history: HistoryPoint[] | undefined, + durationMs: number, + metric: ChartMetricDef | undefined, + isProducer: boolean, + breakerRatingA: number | undefined +): BuildChartResult { + if (!metric) metric = CHART_METRICS[DEFAULT_CHART_METRIC]!; + const accentRgb = isProducer ? "140, 160, 220" : "77, 217, 175"; + const accentColor = `rgb(${accentRgb})`; + const now = Date.now(); + const startTime = now - durationMs; + + const hasFixedRange = metric.fixedMin !== undefined && metric.fixedMax !== undefined; + const unit = metric.unit(0); + + const data: DataPair[] = (history ?? []).filter(p => p.time >= startTime).map((p): DataPair => [p.time, Math.abs(p.value)]); + + const series: SeriesDef[] = [ + { + type: "line", + data, + showSymbol: false, + smooth: false, + lineStyle: { width: 1.5, color: accentColor }, + areaStyle: { + color: { + type: "linear", + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { offset: 0, color: `rgba(${accentRgb}, 0.35)` }, + { offset: 1, color: `rgba(${accentRgb}, 0.02)` }, + ], + }, + }, + itemStyle: { color: accentColor }, + }, + ]; + + const dataMax = data.length > 0 ? safeMax(data) : 0; + const useDecimalAxis = dataMax < 10; + + const yAxis: YAxisDef = { + type: "value", + splitNumber: 4, + axisLabel: { + fontSize: 10, + formatter: useDecimalAxis ? (v: number): string => (v === 0 ? "0" : v.toFixed(1)) : (v: number): string => metric.format(v), + }, + splitLine: { lineStyle: { opacity: 0.15 } }, + }; + + if (hasFixedRange) { + yAxis.min = metric.fixedMin; + yAxis.max = metric.fixedMax; + } else if (dataMax < 1) { + yAxis.min = 0; + yAxis.max = 1; + } + + if (breakerRatingA && metric.entityRole === "current") { + yAxis.min = 0; + yAxis.max = Math.ceil(breakerRatingA * NEC_TRIP_RATING_FACTOR); + + series.push({ + type: "line", + data: [ + [startTime, breakerRatingA * NEC_CONTINUOUS_LOAD_FACTOR], + [now, breakerRatingA * NEC_CONTINUOUS_LOAD_FACTOR], + ], + showSymbol: false, + lineStyle: { width: 1, color: "rgba(255, 200, 40, 0.6)", type: "dashed" }, + itemStyle: { color: "transparent" }, + tooltip: { show: false }, + }); + + series.push({ + type: "line", + data: [ + [startTime, breakerRatingA], + [now, breakerRatingA], + ], + showSymbol: false, + lineStyle: { width: 1.5, color: "rgba(255, 60, 60, 0.7)", type: "solid" }, + itemStyle: { color: "transparent" }, + tooltip: { show: false }, + }); + } + + const options: ChartOptionsDef = { + xAxis: { + type: "time", + min: startTime, + max: now, + axisLabel: { fontSize: 10 }, + splitLine: { show: false }, + }, + yAxis, + grid: { top: 8, right: 4, bottom: 0, left: 0, containLabel: true }, + tooltip: { + trigger: "axis", + axisPointer: { type: "line", lineStyle: { type: "dashed" } }, + formatter: (params: { value: [number, number] }[]): string => { + if (!params || params.length === 0) return ""; + const p = params[0]!; + const date = new Date(p.value[0]); + const timeStr = date.toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + const val = parseFloat(p.value[1].toFixed(2)); + return `
${timeStr}
${val} ${unit}
`; + }, + }, + animation: false, + }; + + return { options, series }; +} diff --git a/src/chart/chart-update.js b/src/chart/chart-update.js deleted file mode 100644 index fd2723f..0000000 --- a/src/chart/chart-update.js +++ /dev/null @@ -1,17 +0,0 @@ -import { buildChartOptions } from "./chart-options.js"; - -export function updateChart(container, hass, history, durationMs, metric, isProducer, heightPx, breakerRatingA) { - const { options, series } = buildChartOptions(history, durationMs, metric, isProducer, breakerRatingA); - let chart = container.querySelector("ha-chart-base"); - if (!chart) { - chart = document.createElement("ha-chart-base"); - chart.style.display = "block"; - chart.style.width = "100%"; - chart.height = (heightPx || 120) + "px"; - container.innerHTML = ""; - container.appendChild(chart); - } - chart.hass = hass; - chart.options = options; - chart.data = series; -} diff --git a/src/chart/chart-update.ts b/src/chart/chart-update.ts new file mode 100644 index 0000000..2e40e79 --- /dev/null +++ b/src/chart/chart-update.ts @@ -0,0 +1,34 @@ +import { buildChartOptions } from "./chart-options.js"; +import type { HomeAssistant, HistoryPoint, ChartMetricDef } from "../types.js"; + +interface HaChartBaseElement extends HTMLElement { + hass: HomeAssistant; + options: unknown; + data: unknown; + height: string; +} + +export function updateChart( + container: HTMLElement, + hass: HomeAssistant, + history: HistoryPoint[] | undefined, + durationMs: number, + metric: ChartMetricDef | undefined, + isProducer: boolean, + heightPx: number | undefined, + breakerRatingA?: number +): void { + const { options, series } = buildChartOptions(history, durationMs, metric, isProducer, breakerRatingA); + let chart = container.querySelector("ha-chart-base") as HaChartBaseElement | null; + if (!chart) { + chart = document.createElement("ha-chart-base") as HaChartBaseElement; + chart.style.display = "block"; + chart.style.width = "100%"; + chart.height = (heightPx ?? 120) + "px"; + container.innerHTML = ""; + container.appendChild(chart); + } + chart.hass = hass; + chart.options = options; + chart.data = series; +} diff --git a/src/constants.js b/src/constants.ts similarity index 59% rename from src/constants.js rename to src/constants.ts index 8060b2b..4ebc0e1 100644 --- a/src/constants.js +++ b/src/constants.ts @@ -1,8 +1,9 @@ import { t } from "./i18n.js"; +import type { ChartMetricDef, GraphHorizonPreset, SheddingPriorityDef } from "./types.js"; export const CARD_VERSION = "0.9.0"; -// ── Defaults ──────────────────────────────────────────────────────────────── +// -- Defaults -- export const DEFAULT_HISTORY_DAYS = 0; export const DEFAULT_HISTORY_HOURS = 0; @@ -10,11 +11,11 @@ export const DEFAULT_HISTORY_MINUTES = 5; export const DEFAULT_CHART_METRIC = "power"; export const LIVE_SAMPLE_INTERVAL_MS = 1000; -// ── Graph time horizon presets ───────────────────────────────────────────── +// -- Graph time horizon presets -- export const DEFAULT_GRAPH_HORIZON = "5m"; -export const GRAPH_HORIZONS = { +export const GRAPH_HORIZONS: Record = { "5m": { ms: 5 * 60 * 1000, refreshMs: 1000, useRealtime: true }, "1h": { ms: 60 * 60 * 1000, refreshMs: 30000, useRealtime: false }, "1d": { ms: 24 * 60 * 60 * 1000, refreshMs: 60000, useRealtime: false }, @@ -22,7 +23,7 @@ export const GRAPH_HORIZONS = { "1M": { ms: 30 * 24 * 60 * 60 * 1000, refreshMs: 60000, useRealtime: false }, }; -// ── Domain / type identifiers ─────────────────────────────────────────────── +// -- Domain / type identifiers -- export const INTEGRATION_DOMAIN = "span_panel"; export const RELAY_STATE_CLOSED = "CLOSED"; @@ -31,14 +32,37 @@ export const SUB_DEVICE_TYPE_BESS = "bess"; export const SUB_DEVICE_TYPE_EVSE = "evse"; export const SUB_DEVICE_KEY_PREFIX = "sub_"; -// ── Chart metric definitions ──────────────────────────────────────────────── +// -- Chart layout constants -- -export const CHART_METRICS = { +export const CIRCUIT_CHART_HEIGHT = 100; +export const CIRCUIT_COL_SPAN_CHART_HEIGHT = 200; +export const BESS_CHART_COL_HEIGHT = 120; +export const EVSE_CHART_HEIGHT = 150; + +// -- NEC breaker limits -- + +export const NEC_CONTINUOUS_LOAD_FACTOR = 0.8; +export const NEC_TRIP_RATING_FACTOR = 1.25; + +// -- History thresholds -- + +export const STATISTICS_PERIOD_THRESHOLD_HOURS = 72; +export const MIN_HISTORY_DURATION_MS = 60_000; + +// -- UI debounce / timing -- + +export const INPUT_DEBOUNCE_MS = 500; +export const THRESHOLD_DEBOUNCE_MS = 800; +export const ERROR_DISPLAY_MS = 5_000; + +// -- Chart metric definitions -- + +export const CHART_METRICS: Record = { power: { entityRole: "power", label: () => t("metric.power"), - unit: v => (Math.abs(v) >= 1000 ? "kW" : "W"), - format: v => { + unit: (v: number) => (Math.abs(v) >= 1000 ? "kW" : "W"), + format: (v: number) => { const abs = Math.abs(v); if (abs >= 1000) return (abs / 1000).toFixed(1); if (abs < 10 && abs > 0) return abs.toFixed(1); @@ -49,29 +73,31 @@ export const CHART_METRICS = { entityRole: "current", label: () => t("metric.current"), unit: () => "A", - format: v => Math.abs(v).toFixed(1), + format: (v: number) => Math.abs(v).toFixed(1), }, }; -export const BESS_CHART_METRICS = { +export const BESS_CHART_METRICS: Record = { soc: { + entityRole: "soc", label: () => t("metric.soc"), unit: () => "%", - format: v => String(Math.round(v)), + format: (v: number) => String(Math.round(v)), fixedMin: 0, fixedMax: 100, }, soe: { + entityRole: "soe", label: () => t("metric.soe"), unit: () => "kWh", - format: v => v.toFixed(1), + format: (v: number) => v.toFixed(1), }, - power: CHART_METRICS.power, + power: CHART_METRICS.power!, }; -// ── Shedding priority ────────────────────────────────────────────────────── +// -- Shedding priority -- -export const SHEDDING_PRIORITIES = { +export const SHEDDING_PRIORITIES: Record = { always_on: { icon: "mdi:battery", icon2: "mdi:router-wireless", color: "#4caf50", label: () => t("shedding.always_on") }, never: { icon: "mdi:battery", color: "#4caf50", label: () => t("shedding.never") }, soc_threshold: { icon: "mdi:battery-alert-variant-outline", color: "#9c27b0", label: () => t("shedding.soc_threshold"), textLabel: "SoC" }, @@ -79,7 +105,7 @@ export const SHEDDING_PRIORITIES = { unknown: { icon: "mdi:help-circle-outline", color: "#888", label: () => t("shedding.unknown") }, }; -export const MONITORING_COLORS = { +export const MONITORING_COLORS: Record = { normal: "#4caf50", warning: "#ff9800", alert: "#f44336", diff --git a/src/core/dashboard-controller.ts b/src/core/dashboard-controller.ts new file mode 100644 index 0000000..24a7f1c --- /dev/null +++ b/src/core/dashboard-controller.ts @@ -0,0 +1,414 @@ +import { DEFAULT_GRAPH_HORIZON, GRAPH_HORIZONS, LIVE_SAMPLE_INTERVAL_MS } from "../constants.js"; +import { getCircuitChartEntity } from "../helpers/chart.js"; +import { getHorizonDurationMs, getMaxHistoryPoints, getMinGapMs, recordSample } from "../helpers/history.js"; +import { loadHistory, collectSubDeviceEntityIds } from "./history-loader.js"; +import { updateCircuitDOM, updateSubDeviceDOM } from "./dom-updater.js"; +import { getEffectiveHorizon, getEffectiveSubDeviceHorizon } from "./graph-settings.js"; +import { MonitoringStatusCache } from "./monitoring-status.js"; +import { GraphSettingsCache } from "./graph-settings.js"; +import type { HomeAssistant, PanelTopology, CardConfig, HistoryMap, GraphSettings } from "../types.js"; + +const RECORDER_REFRESH_MS = 30_000; +const RESIZE_THRESHOLD_PX = 5; +const RESIZE_DEBOUNCE_MS = 150; +const SLIDE_THRESHOLD = 0.9; + +type DOMRoot = Element | ShadowRoot; + +interface SpanSidePanelElement extends HTMLElement { + hass: HomeAssistant; + open(config: Record): void; +} + +/** + * Shared controller encapsulating dashboard behavior used by both + * the Lovelace card (SpanPanelCard) and the integration panel (DashboardTab). + */ +export class DashboardController { + readonly powerHistory: HistoryMap = new Map(); + readonly horizonMap: Map = new Map(); + readonly subDeviceHorizonMap: Map = new Map(); + readonly monitoringCache = new MonitoringStatusCache(); + readonly graphSettingsCache = new GraphSettingsCache(); + + private _hass: HomeAssistant | null = null; + private _topology: PanelTopology | null = null; + private _config: CardConfig | null = null; + private _configEntryId: string | null = null; + + private _updateInterval: ReturnType | null = null; + private _recorderRefreshInterval: ReturnType | null = null; + private _resizeObserver: ResizeObserver | null = null; + private _lastWidth = 0; + private _resizeDebounce: ReturnType | null = null; + + get hass(): HomeAssistant | null { + return this._hass; + } + + set hass(val: HomeAssistant | null) { + this._hass = val; + } + + get topology(): PanelTopology | null { + return this._topology; + } + + get config(): CardConfig | null { + return this._config; + } + + init(topology: PanelTopology | null, config: CardConfig, hass: HomeAssistant | null, configEntryId: string | null): void { + this._topology = topology; + this._config = config; + this._hass = hass; + this._configEntryId = configEntryId; + } + + setConfig(config: CardConfig): void { + this._config = config; + } + + buildHorizonMaps(settings: GraphSettings | null): void { + if (settings && this._topology?.circuits) { + for (const uuid of Object.keys(this._topology.circuits)) { + this.horizonMap.set(uuid, getEffectiveHorizon(settings, uuid)); + } + } + if (settings && this._topology?.sub_devices) { + for (const devId of Object.keys(this._topology.sub_devices)) { + this.subDeviceHorizonMap.set(devId, getEffectiveSubDeviceHorizon(settings, devId)); + } + } + } + + async fetchAndBuildHorizonMaps(): Promise { + try { + await this.graphSettingsCache.fetch(this._hass!, this._configEntryId); + this.buildHorizonMaps(this.graphSettingsCache.settings); + } catch { + // Graph settings unavailable -- use defaults + } + } + + async loadHistory(): Promise { + await loadHistory(this._hass!, this._topology!, this._config!, this.powerHistory, this.horizonMap, this.subDeviceHorizonMap); + } + + recordSamples(): void { + if (!this._topology || !this._hass || !this._config) return; + const now = Date.now(); + + for (const [uuid, circuit] of Object.entries(this._topology.circuits)) { + const horizon = this.horizonMap.get(uuid) ?? DEFAULT_GRAPH_HORIZON; + if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; + + const entityId = getCircuitChartEntity(circuit, this._config); + if (!entityId) continue; + const state = this._hass.states[entityId]; + if (!state) continue; + const val = parseFloat(state.state); + if (isNaN(val)) continue; + + const durationMs = getHorizonDurationMs(horizon); + const maxPoints = getMaxHistoryPoints(durationMs); + const minGap = getMinGapMs(durationMs); + const cutoff = now - durationMs; + + const hist = this.powerHistory.get(uuid) ?? []; + if (hist.length > 0 && now - hist[hist.length - 1]!.time < minGap) continue; + + recordSample(this.powerHistory, uuid, val, now, cutoff, maxPoints); + } + + for (const { entityId, key, devId } of collectSubDeviceEntityIds(this._topology)) { + const horizon = this.subDeviceHorizonMap.get(devId) ?? DEFAULT_GRAPH_HORIZON; + if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; + + const state = this._hass.states[entityId]; + if (!state) continue; + const val = parseFloat(state.state); + if (isNaN(val)) continue; + + const durationMs = getHorizonDurationMs(horizon); + const maxPoints = getMaxHistoryPoints(durationMs); + const minGap = getMinGapMs(durationMs); + const cutoff = now - durationMs; + + const hist = this.powerHistory.get(key) ?? []; + if (hist.length > 0 && now - hist[hist.length - 1]!.time < minGap) continue; + + recordSample(this.powerHistory, key, val, now, cutoff, maxPoints); + } + } + + async refreshRecorderData(root: DOMRoot): Promise { + if (!this._topology || !this._hass || !this._config) return; + + const nonRealtimeMap = new Map(); + for (const [uuid, horizon] of this.horizonMap) { + if (!GRAPH_HORIZONS[horizon]?.useRealtime) { + nonRealtimeMap.set(uuid, horizon); + } + } + if (nonRealtimeMap.size === 0) return; + + const freshHistory: HistoryMap = new Map(); + try { + await loadHistory(this._hass, this._topology, this._config, freshHistory, nonRealtimeMap, this.subDeviceHorizonMap); + for (const uuid of nonRealtimeMap.keys()) { + const data = freshHistory.get(uuid); + if (data) { + this.powerHistory.set(uuid, data); + } else { + this.powerHistory.delete(uuid); + } + } + this.updateDOM(root); + } catch { + // Will refresh on next interval + } + } + + updateDOM(root: DOMRoot): void { + if (!this._hass || !this._topology || !this._config) return; + updateCircuitDOM(root, this._hass, this._topology, this._config, this.powerHistory, this.horizonMap); + updateSubDeviceDOM(root, this._hass, this._topology, this._config, this.powerHistory, this.subDeviceHorizonMap); + } + + async onGraphSettingsChanged(root: DOMRoot): Promise { + if (!this._hass) return; + this.graphSettingsCache.invalidate(); + await this.graphSettingsCache.fetch(this._hass, this._configEntryId); + this.buildHorizonMaps(this.graphSettingsCache.settings); + + this.powerHistory.clear(); + try { + await this.loadHistory(); + } catch { + // Will populate on next refresh + } + this.updateDOM(root); + } + + onToggleClick(ev: Event, root: DOMRoot): void { + const target = ev.target as HTMLElement | null; + const pill = target?.closest(".toggle-pill"); + if (!pill) return; + const cb = root.querySelector(".slide-confirm"); + if (!cb || !cb.classList.contains("confirmed")) return; + ev.stopPropagation(); + ev.preventDefault(); + const slot = pill.closest("[data-uuid]") as HTMLElement | null; + if (!slot || !this._topology || !this._hass) return; + const uuid = slot.dataset.uuid; + if (!uuid) return; + const circuit = this._topology.circuits[uuid]; + if (!circuit) return; + const switchEntity = circuit.entities?.switch; + if (!switchEntity) return; + const switchState = this._hass.states[switchEntity]; + if (!switchState) { + console.warn("SPAN Panel: switch entity not found:", switchEntity); + return; + } + const service = switchState.state === "on" ? "turn_off" : "turn_on"; + this._hass.callService("switch", service, {}, { entity_id: switchEntity }).catch(err => { + console.error("SPAN Panel: switch service call failed:", err); + }); + } + + async onGearClick(event: Event, root: DOMRoot): Promise { + const target = event.target as HTMLElement | null; + const gearBtn = target?.closest(".gear-icon") as HTMLElement | null; + if (!gearBtn) return; + + const sidePanel = root.querySelector("span-side-panel") as SpanSidePanelElement | null; + if (!sidePanel || !this._hass) return; + sidePanel.hass = this._hass; + + if (gearBtn.classList.contains("panel-gear")) { + await this.graphSettingsCache.fetch(this._hass, this._configEntryId); + sidePanel.open({ + panelMode: true, + topology: this._topology, + graphSettings: this.graphSettingsCache.settings, + }); + return; + } + + const uuid = gearBtn.dataset.uuid; + if (uuid && this._topology) { + const circuit = this._topology.circuits[uuid]; + if (circuit) { + await this.monitoringCache.fetch(this._hass, this._configEntryId); + const powerEntity = circuit.entities?.power; + const monitoringInfo = powerEntity ? (this.monitoringCache.status?.circuits?.[powerEntity] ?? null) : null; + + await this.graphSettingsCache.fetch(this._hass, this._configEntryId); + const graphSettings = this.graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon ?? DEFAULT_GRAPH_HORIZON; + const circuitOverride = graphSettings?.circuits?.[uuid]; + const graphHorizonInfo = circuitOverride ? { ...circuitOverride, globalHorizon } : { horizon: globalHorizon, has_override: false, globalHorizon }; + + sidePanel.open({ + ...circuit, + uuid, + monitoringInfo, + graphHorizonInfo, + } as Record); + return; + } + } + + const subDevId = gearBtn.dataset.subdevId; + if (subDevId && this._topology?.sub_devices?.[subDevId]) { + const sub = this._topology.sub_devices[subDevId]!; + + await this.graphSettingsCache.fetch(this._hass, this._configEntryId); + const graphSettings = this.graphSettingsCache.settings; + const globalHorizon = graphSettings?.global_horizon ?? DEFAULT_GRAPH_HORIZON; + const subOverride = graphSettings?.sub_devices?.[subDevId]; + const graphHorizonInfo = subOverride ? { ...subOverride, globalHorizon } : { horizon: globalHorizon, has_override: false, globalHorizon }; + + sidePanel.open({ + subDeviceMode: true, + subDeviceId: subDevId, + name: sub.name ?? subDevId, + deviceType: sub.type ?? "", + graphHorizonInfo, + }); + } + } + + bindSlideConfirm(slideEl: Element, parent: Element | null): void { + const knob = slideEl.querySelector(".slide-confirm-knob") as HTMLElement | null; + const textEl = slideEl.querySelector(".slide-confirm-text"); + if (!knob || !textEl) return; + let dragging = false; + let startX = 0; + let maxX = 0; + + const begin = (clientX: number): void => { + if (slideEl.classList.contains("confirmed")) return; + dragging = true; + startX = clientX - knob.offsetLeft; + maxX = (slideEl as HTMLElement).offsetWidth - knob.offsetWidth - 4; + knob.classList.remove("snapping"); + }; + const move = (clientX: number): void => { + if (!dragging) return; + const x = Math.max(2, Math.min(clientX - startX, maxX)); + knob.style.left = x + "px"; + }; + const end = (): void => { + if (!dragging) return; + dragging = false; + const pos = (knob.offsetLeft - 2) / maxX; + if (pos >= SLIDE_THRESHOLD) { + knob.style.left = maxX + "px"; + slideEl.classList.add("confirmed"); + knob.querySelector("ha-icon")?.setAttribute("icon", "mdi:lock-open"); + textEl.textContent = (slideEl as HTMLElement).dataset.textOn ?? ""; + if (parent) parent.classList.remove("switches-disabled"); + } else { + knob.classList.add("snapping"); + knob.style.left = "2px"; + } + }; + + knob.addEventListener("mousedown", (e: MouseEvent) => { + e.preventDefault(); + begin(e.clientX); + }); + slideEl.addEventListener("mousemove", (e: Event) => move((e as MouseEvent).clientX)); + slideEl.addEventListener("mouseup", end); + slideEl.addEventListener("mouseleave", end); + knob.addEventListener( + "touchstart", + (e: TouchEvent) => { + e.preventDefault(); + begin(e.touches[0]!.clientX); + }, + { passive: false } + ); + slideEl.addEventListener("touchmove", (e: Event) => move((e as TouchEvent).touches[0]!.clientX), { passive: true }); + slideEl.addEventListener("touchend", end); + slideEl.addEventListener("touchcancel", end); + + slideEl.addEventListener("click", () => { + if (!slideEl.classList.contains("confirmed")) return; + slideEl.classList.remove("confirmed"); + knob.classList.add("snapping"); + knob.style.left = "2px"; + knob.querySelector("ha-icon")?.setAttribute("icon", "mdi:lock"); + textEl.textContent = (slideEl as HTMLElement).dataset.textOff ?? ""; + if (parent) parent.classList.add("switches-disabled"); + }); + } + + startIntervals(root: DOMRoot, onUpdate?: () => void): void { + this._updateInterval = setInterval(() => { + this.recordSamples(); + this.updateDOM(root); + if (onUpdate) onUpdate(); + }, LIVE_SAMPLE_INTERVAL_MS); + + this._recorderRefreshInterval = setInterval(() => { + this.refreshRecorderData(root); + }, RECORDER_REFRESH_MS); + } + + stopIntervals(): void { + if (this._updateInterval) { + clearInterval(this._updateInterval); + this._updateInterval = null; + } + if (this._recorderRefreshInterval) { + clearInterval(this._recorderRefreshInterval); + this._recorderRefreshInterval = null; + } + this.cleanupResizeObserver(); + } + + setupResizeObserver(root: DOMRoot, element: Element | null): void { + this.cleanupResizeObserver(); + if (!element) return; + this._lastWidth = (element as HTMLElement).clientWidth; + this._resizeObserver = new ResizeObserver(entries => { + const entry = entries[0]; + if (!entry) return; + const newWidth = entry.contentRect.width; + if (Math.abs(newWidth - this._lastWidth) < RESIZE_THRESHOLD_PX) return; + this._lastWidth = newWidth; + if (this._resizeDebounce) clearTimeout(this._resizeDebounce); + this._resizeDebounce = setTimeout(() => { + for (const container of root.querySelectorAll(".chart-container")) { + const chart = container.querySelector("ha-chart-base"); + if (chart) chart.remove(); + } + this.updateDOM(root); + }, RESIZE_DEBOUNCE_MS); + }); + this._resizeObserver.observe(element); + } + + cleanupResizeObserver(): void { + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } + if (this._resizeDebounce) { + clearTimeout(this._resizeDebounce); + this._resizeDebounce = null; + } + } + + reset(): void { + this.powerHistory.clear(); + this.horizonMap.clear(); + this.subDeviceHorizonMap.clear(); + this.monitoringCache.clear(); + this.graphSettingsCache.clear(); + } +} diff --git a/src/core/dom-updater.js b/src/core/dom-updater.ts similarity index 79% rename from src/core/dom-updater.js rename to src/core/dom-updater.ts index aa17676..aa6d322 100644 --- a/src/core/dom-updater.js +++ b/src/core/dom-updater.ts @@ -1,14 +1,24 @@ -import { BESS_CHART_METRICS, DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES } from "../constants.js"; +import { + BESS_CHART_METRICS, + DEVICE_TYPE_PV, + RELAY_STATE_CLOSED, + SHEDDING_PRIORITIES, + CIRCUIT_CHART_HEIGHT, + CIRCUIT_COL_SPAN_CHART_HEIGHT, + BESS_CHART_COL_HEIGHT, + EVSE_CHART_HEIGHT, +} from "../constants.js"; import { formatPowerSigned, formatPowerUnit, formatKw } from "../helpers/format.js"; import { t } from "../i18n.js"; import { getChartMetric } from "../helpers/chart.js"; import { findSubDevicePowerEntity } from "../helpers/entity-finder.js"; import { getHistoryDurationMs, getHorizonDurationMs } from "../helpers/history.js"; import { updateChart } from "../chart/chart-update.js"; +import type { HomeAssistant, PanelTopology, CardConfig, HistoryMap, ChartMetricDef } from "../types.js"; // ── Header stats ─────────────────────────────────────────────────────────── -function _updateHeaderStats(root, hass, topology, config, totalConsumption) { +function _updateHeaderStats(root: Element | ShadowRoot, hass: HomeAssistant, topology: PanelTopology, config: CardConfig, totalConsumption: number): void { const isAmpsMode = (config.chart_metric || "power") === "current"; // Site / consumption stat @@ -17,7 +27,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption) { if (isAmpsMode) { const siteEid = topology.panel_entities?.site_power; const siteState = siteEid ? hass.states[siteEid] : null; - const amps = siteState ? parseFloat(siteState.attributes?.amperage) : NaN; + const amps = siteState ? parseFloat(siteState.attributes?.amperage as string) : NaN; if (consumptionEl) consumptionEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; if (consumptionUnitEl) consumptionUnitEl.textContent = "A"; } else { @@ -37,7 +47,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption) { const upEid = topology.panel_entities?.current_power; const upState = upEid ? hass.states[upEid] : null; if (isAmpsMode) { - const amps = upState ? parseFloat(upState.attributes?.amperage) : NaN; + const amps = upState ? parseFloat(upState.attributes?.amperage as string) : NaN; upstreamEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; if (upstreamUnitEl) upstreamUnitEl.textContent = "A"; } else { @@ -54,7 +64,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption) { const downEid = topology.panel_entities?.feedthrough_power; const downState = downEid ? hass.states[downEid] : null; if (isAmpsMode) { - const amps = downState ? parseFloat(downState.attributes?.amperage) : NaN; + const amps = downState ? parseFloat(downState.attributes?.amperage as string) : NaN; downstreamEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; if (downstreamUnitEl) downstreamUnitEl.textContent = "A"; } else { @@ -71,7 +81,7 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption) { const solarEid = topology.panel_entities?.pv_power; const solarState = solarEid ? hass.states[solarEid] : null; if (isAmpsMode) { - const amps = solarState ? parseFloat(solarState.attributes?.amperage) : NaN; + const amps = solarState ? parseFloat(solarState.attributes?.amperage as string) : NaN; solarEl.textContent = Number.isFinite(amps) ? Math.abs(amps).toFixed(1) : "--"; if (solarUnitEl) solarUnitEl.textContent = "A"; } else { @@ -104,7 +114,14 @@ function _updateHeaderStats(root, hass, topology, config, totalConsumption) { // ── Exported updaters ────────────────────────────────────────────────────── -export function updateCircuitDOM(root, hass, topology, config, powerHistory, horizonMap) { +export function updateCircuitDOM( + root: Element | ShadowRoot, + hass: HomeAssistant, + topology: PanelTopology, + config: CardConfig, + powerHistory: HistoryMap, + horizonMap: Map | undefined +): void { if (!root || !topology || !hass) return; const defaultDurationMs = getHistoryDurationMs(config); @@ -122,7 +139,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor _updateHeaderStats(root, hass, topology, config, totalConsumption); - const chartMetric = getChartMetric(config); + const chartMetric: ChartMetricDef = getChartMetric(config); const showCurrent = chartMetric.entityRole === "current"; for (const [uuid, circuit] of Object.entries(topology.circuits)) { @@ -136,7 +153,9 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor const switchEntityId = circuit.entities?.switch; const switchState = switchEntityId ? hass.states[switchEntityId] : null; - const isOn = switchState ? switchState.state === "on" : (state?.attributes?.relay_state || circuit.relay_state) === RELAY_STATE_CLOSED; + const isOn = switchState + ? switchState.state === "on" + : ((state?.attributes?.relay_state as string | undefined) || circuit.relay_state) === RELAY_STATE_CLOSED; const powerVal = slot.querySelector(".power-value"); if (powerVal) { @@ -150,7 +169,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor } } - const toggle = slot.querySelector(".toggle-pill"); + const toggle = slot.querySelector(".toggle-pill") as HTMLElement | null; if (toggle) { toggle.className = `toggle-pill ${isOn ? "toggle-on" : "toggle-off"}`; const label = toggle.querySelector(".toggle-label"); @@ -161,7 +180,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor slot.classList.toggle("circuit-producer", isProducer); // Update shedding priority icon - let priority; + let priority: string; if (circuit.always_on) { priority = "always_on"; } else { @@ -169,15 +188,16 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor const selectState = selectEid ? hass.states[selectEid] : null; priority = selectState ? selectState.state : "unknown"; } - const shedInfo = SHEDDING_PRIORITIES[priority] || SHEDDING_PRIORITIES.unknown; - const sheddingIcon = slot.querySelector(".shedding-icon"); + // "unknown" key always exists in SHEDDING_PRIORITIES, so the fallback is guaranteed + const shedInfo = (SHEDDING_PRIORITIES[priority] ?? SHEDDING_PRIORITIES["unknown"])!; + const sheddingIcon = slot.querySelector(".shedding-icon") as HTMLElement | null; if (sheddingIcon) { sheddingIcon.setAttribute("icon", shedInfo.icon); sheddingIcon.style.color = shedInfo.color; sheddingIcon.title = shedInfo.label(); } // Update secondary icon if present - const secondaryIcon = slot.querySelector(".shedding-icon-secondary"); + const secondaryIcon = slot.querySelector(".shedding-icon-secondary") as HTMLElement | null; if (secondaryIcon) { if (shedInfo.icon2) { secondaryIcon.setAttribute("icon", shedInfo.icon2); @@ -188,7 +208,7 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor } } // Update text label if present - const sheddingLabel = slot.querySelector(".shedding-label"); + const sheddingLabel = slot.querySelector(".shedding-label") as HTMLElement | null; if (sheddingLabel) { if (shedInfo.textLabel) { sheddingLabel.textContent = shedInfo.textLabel; @@ -199,17 +219,24 @@ export function updateCircuitDOM(root, hass, topology, config, powerHistory, hor } } - const chartContainer = slot.querySelector(".chart-container"); + const chartContainer = slot.querySelector(".chart-container") as HTMLElement | null; if (chartContainer) { const history = powerHistory.get(uuid) || []; - const h = slot.classList.contains("circuit-col-span") ? 200 : 100; - const circuitDuration = horizonMap?.has(uuid) ? getHorizonDurationMs(horizonMap.get(uuid)) : defaultDurationMs; - updateChart(chartContainer, hass, history, circuitDuration, chartMetric, isProducer, h, circuit.breaker_rating_a); + const h = slot.classList.contains("circuit-col-span") ? CIRCUIT_COL_SPAN_CHART_HEIGHT : CIRCUIT_CHART_HEIGHT; + const circuitDuration = horizonMap?.has(uuid) ? getHorizonDurationMs(horizonMap.get(uuid)!) : defaultDurationMs; + updateChart(chartContainer, hass, history, circuitDuration, chartMetric, isProducer, h, circuit.breaker_rating_a ?? undefined); } } } -export function updateSubDeviceDOM(root, hass, topology, config, powerHistory, subDeviceHorizonMap) { +export function updateSubDeviceDOM( + root: Element | ShadowRoot, + hass: HomeAssistant, + topology: PanelTopology, + config: CardConfig, + powerHistory: HistoryMap, + subDeviceHorizonMap: Map | undefined +): void { if (!topology.sub_devices) return; const defaultDurationMs = getHistoryDurationMs(config); @@ -229,14 +256,15 @@ export function updateSubDeviceDOM(root, hass, topology, config, powerHistory, s const chartContainers = section.querySelectorAll("[data-chart-key]"); for (const cc of chartContainers) { - const chartKey = cc.dataset.chartKey; + const chartKey = (cc as HTMLElement).dataset.chartKey; + if (!chartKey) continue; const history = powerHistory.get(chartKey) || []; - let metric = BESS_CHART_METRICS.power; - if (chartKey.endsWith("_soc")) metric = BESS_CHART_METRICS.soc; - else if (chartKey.endsWith("_soe")) metric = BESS_CHART_METRICS.soe; + let metric: ChartMetricDef = BESS_CHART_METRICS["power"]!; + if (chartKey.endsWith("_soc")) metric = BESS_CHART_METRICS["soc"]!; + else if (chartKey.endsWith("_soe")) metric = BESS_CHART_METRICS["soe"]!; const isBessCol = !!cc.closest(".bess-chart-col"); - const devDuration = subDeviceHorizonMap?.has(devId) ? getHorizonDurationMs(subDeviceHorizonMap.get(devId)) : defaultDurationMs; - updateChart(cc, hass, history, devDuration, metric, false, isBessCol ? 120 : 150); + const devDuration = subDeviceHorizonMap?.has(devId) ? getHorizonDurationMs(subDeviceHorizonMap.get(devId)!) : defaultDurationMs; + updateChart(cc as HTMLElement, hass, history, devDuration, metric, false, isBessCol ? BESS_CHART_COL_HEIGHT : EVSE_CHART_HEIGHT); } for (const entityId of Object.keys(sub.entities || {})) { @@ -244,7 +272,8 @@ export function updateSubDeviceDOM(root, hass, topology, config, powerHistory, s if (!valEl) continue; const state = hass.states[entityId]; if (state) { - valEl.textContent = `${state.state}${state.attributes.unit_of_measurement ? " " + state.attributes.unit_of_measurement : ""}`; + const unit = state.attributes.unit_of_measurement as string | undefined; + valEl.textContent = `${state.state}${unit ? " " + unit : ""}`; } } } diff --git a/src/core/graph-settings.js b/src/core/graph-settings.ts similarity index 62% rename from src/core/graph-settings.js rename to src/core/graph-settings.ts index ccc6bb1..b347158 100644 --- a/src/core/graph-settings.js +++ b/src/core/graph-settings.ts @@ -1,13 +1,22 @@ -// src/core/graph-settings.js +// src/core/graph-settings.ts import { INTEGRATION_DOMAIN, DEFAULT_GRAPH_HORIZON } from "../constants.js"; +import type { HomeAssistant, GraphSettings } from "../types.js"; const GRAPH_SETTINGS_POLL_INTERVAL_MS = 30_000; +interface GraphSettingsServiceResponse { + response?: GraphSettings; +} + /** * Caches graph horizon settings fetched via the get_graph_settings service. * Re-fetches at most every 30 seconds unless invalidated. */ export class GraphSettingsCache { + private _settings: GraphSettings | null; + private _lastFetch: number; + private _fetching: boolean; + constructor() { this._settings = null; this._lastFetch = 0; @@ -16,11 +25,8 @@ export class GraphSettingsCache { /** * Fetch graph settings, returning cached data if recent. - * @param {object} hass - Home Assistant instance - * @param {string} [configEntryId] - Optional config entry ID - * @returns {Promise} Graph settings or null */ - async fetch(hass, configEntryId) { + async fetch(hass: HomeAssistant, configEntryId?: string | null): Promise { const now = Date.now(); if (this._fetching) return this._settings; if (this._settings && now - this._lastFetch < GRAPH_SETTINGS_POLL_INTERVAL_MS) { @@ -29,16 +35,16 @@ export class GraphSettingsCache { this._fetching = true; try { - const serviceData = {}; + const serviceData: Record = {}; if (configEntryId) serviceData.config_entry_id = configEntryId; - const resp = await hass.callWS({ + const resp = await hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, service: "get_graph_settings", service_data: serviceData, return_response: true, }); - this._settings = resp?.response || null; + this._settings = resp?.response ?? null; this._lastFetch = now; } catch { this._settings = null; @@ -49,17 +55,17 @@ export class GraphSettingsCache { } /** Force the next fetch() call to re-query the backend. */ - invalidate() { + invalidate(): void { this._lastFetch = 0; } - /** @returns {object|null} Last fetched settings */ - get settings() { + /** Last fetched settings. */ + get settings(): GraphSettings | null { return this._settings; } /** Clear cached settings (e.g., on config change). */ - clear() { + clear(): void { this._settings = null; this._lastFetch = 0; } @@ -67,26 +73,20 @@ export class GraphSettingsCache { /** * Get the effective horizon for a circuit. - * @param {object|null} settings - Full graph settings from get_graph_settings - * @param {string} circuitId - Circuit identifier - * @returns {string} Horizon key (e.g., "5m", "1h") */ -export function getEffectiveHorizon(settings, circuitId) { +export function getEffectiveHorizon(settings: GraphSettings | null, circuitId: string): string { if (!settings) return DEFAULT_GRAPH_HORIZON; const override = settings.circuits?.[circuitId]; if (override?.has_override) return override.horizon; - return settings.global_horizon || DEFAULT_GRAPH_HORIZON; + return settings.global_horizon ?? DEFAULT_GRAPH_HORIZON; } /** * Get the effective horizon for a sub-device. - * @param {object|null} settings - Full graph settings from get_graph_settings - * @param {string} subDeviceId - Sub-device identifier - * @returns {string} Horizon key (e.g., "5m", "1h") */ -export function getEffectiveSubDeviceHorizon(settings, subDeviceId) { +export function getEffectiveSubDeviceHorizon(settings: GraphSettings | null, subDeviceId: string): string { if (!settings) return DEFAULT_GRAPH_HORIZON; const override = settings.sub_devices?.[subDeviceId]; if (override?.has_override) return override.horizon; - return settings.global_horizon || DEFAULT_GRAPH_HORIZON; + return settings.global_horizon ?? DEFAULT_GRAPH_HORIZON; } diff --git a/src/core/grid-renderer.js b/src/core/grid-renderer.ts similarity index 77% rename from src/core/grid-renderer.js rename to src/core/grid-renderer.ts index 761512b..86330f2 100644 --- a/src/core/grid-renderer.js +++ b/src/core/grid-renderer.ts @@ -5,32 +5,40 @@ import { tabToRow, tabToCol, classifyDualTab } from "../helpers/layout.js"; import { getChartMetric } from "../helpers/chart.js"; import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES, MONITORING_COLORS } from "../constants.js"; import { getCircuitMonitoringInfo, hasCustomOverrides, getUtilizationClass, isAlertActive } from "./monitoring-status.js"; +import type { PanelTopology, Circuit, HomeAssistant, CardConfig, MonitoringStatus, MonitoringPointInfo, SheddingPriorityDef } from "../types.js"; + +type SlotLayout = "single" | "row-span" | "col-span"; + +interface TabMapEntry { + uuid: string; + circuit: Circuit; + layout: SlotLayout; +} /** * Build the full grid HTML for the panel breaker grid. - * - * @param {object} topology - The panel topology object. - * @param {number} totalRows - Total number of breaker rows. - * @param {number} durationMs - History duration in milliseconds. - * @param {object} hass - Home Assistant object. - * @param {object} config - Card configuration object. - * @returns {string} HTML string for the grid. */ -export function buildGridHTML(topology, totalRows, durationMs, hass, config, monitoringStatus) { - const tabMap = new Map(); - const occupiedTabs = new Set(); +export function buildGridHTML( + topology: PanelTopology, + totalRows: number, + hass: HomeAssistant, + config: CardConfig, + monitoringStatus: MonitoringStatus | null +): string { + const tabMap = new Map(); + const occupiedTabs = new Set(); for (const [uuid, circuit] of Object.entries(topology.circuits)) { const tabs = circuit.tabs; if (!tabs || tabs.length === 0) continue; const primaryTab = Math.min(...tabs); - const layout = tabs.length === 1 ? "single" : classifyDualTab(tabs); + const layout: SlotLayout = tabs.length === 1 ? "single" : (classifyDualTab(tabs) ?? "single"); tabMap.set(primaryTab, { uuid, circuit, layout }); - for (const t of tabs) occupiedTabs.add(t); + for (const tab of tabs) occupiedTabs.add(tab); } - const rowsToSkipLeft = new Set(); - const rowsToSkipRight = new Set(); + const rowsToSkipLeft = new Set(); + const rowsToSkipRight = new Set(); for (const [primaryTab, entry] of tabMap) { if (entry.layout === "col-span") { @@ -43,10 +51,13 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config, mon } } - function lookupMonitoring(entry) { - const circuitEntityId = entry.circuit.entities?.current || entry.circuit.entities?.power; - const monInfo = monitoringStatus ? getCircuitMonitoringInfo(monitoringStatus, circuitEntityId) : null; - let sheddingPriority; + function lookupMonitoring(entry: TabMapEntry): { + monInfo: MonitoringPointInfo | null; + sheddingPriority: string; + } { + const circuitEntityId = entry.circuit.entities?.current ?? entry.circuit.entities?.power; + const monInfo = monitoringStatus ? getCircuitMonitoringInfo(monitoringStatus, circuitEntityId ?? "") : null; + let sheddingPriority: string; if (entry.circuit.always_on) { sheddingPriority = "always_on"; } else { @@ -67,7 +78,7 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config, mon if (leftEntry && leftEntry.layout === "row-span") { const { monInfo, sheddingPriority } = lookupMonitoring(leftEntry); - gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 4", "row-span", durationMs, hass, config, monInfo, sheddingPriority); + gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 4", "row-span", hass, config, monInfo, sheddingPriority); gridHTML += `
${rightTab}
`; continue; } @@ -75,7 +86,7 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config, mon if (!rowsToSkipLeft.has(row)) { if (leftEntry && (leftEntry.layout === "col-span" || leftEntry.layout === "single")) { const { monInfo, sheddingPriority } = lookupMonitoring(leftEntry); - gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2", leftEntry.layout, durationMs, hass, config, monInfo, sheddingPriority); + gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2", leftEntry.layout, hass, config, monInfo, sheddingPriority); } else if (!occupiedTabs.has(leftTab)) { gridHTML += renderEmptySlot(row, "2"); } @@ -84,7 +95,7 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config, mon if (!rowsToSkipRight.has(row)) { if (rightEntry && (rightEntry.layout === "col-span" || rightEntry.layout === "single")) { const { monInfo, sheddingPriority } = lookupMonitoring(rightEntry); - gridHTML += renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "3", rightEntry.layout, durationMs, hass, config, monInfo, sheddingPriority); + gridHTML += renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "3", rightEntry.layout, hass, config, monInfo, sheddingPriority); } else if (!occupiedTabs.has(rightTab)) { gridHTML += renderEmptySlot(row, "3"); } @@ -97,18 +108,18 @@ export function buildGridHTML(topology, totalRows, durationMs, hass, config, mon /** * Render a single circuit breaker slot. - * - * @param {string} uuid - Circuit UUID. - * @param {object} circuit - Circuit data object. - * @param {number} row - Grid row number. - * @param {string} col - Grid column value (CSS grid-column). - * @param {string} layout - Layout type: "single", "row-span", or "col-span". - * @param {number} _durationMs - History duration in milliseconds (reserved for future use). - * @param {object} hass - Home Assistant object. - * @param {object} config - Card configuration object. - * @returns {string} HTML string for the circuit slot. */ -export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, hass, config, monitoringInfo, sheddingPriority) { +export function renderCircuitSlot( + uuid: string, + circuit: Circuit, + row: number, + col: string, + layout: SlotLayout, + hass: HomeAssistant, + config: CardConfig, + monitoringInfo: MonitoringPointInfo | null, + sheddingPriority: string +): string { const entityId = circuit.entities?.power; const state = entityId ? hass.states[entityId] : null; const powerW = state ? parseFloat(state.state) || 0 : 0; @@ -116,7 +127,9 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, const switchEntityId = circuit.entities?.switch; const switchState = switchEntityId ? hass.states[switchEntityId] : null; - const isOn = switchState ? switchState.state === "on" : (state?.attributes?.relay_state || circuit.relay_state) === RELAY_STATE_CLOSED; + const isOn = switchState + ? switchState.state === "on" + : ((state?.attributes?.relay_state as string | undefined) || circuit.relay_state) === RELAY_STATE_CLOSED; const breakerAmps = circuit.breaker_rating_a; const breakerLabel = breakerAmps ? `${Math.round(breakerAmps)}A` : ""; @@ -124,7 +137,7 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, const chartMetric = getChartMetric(config); const showCurrent = chartMetric.entityRole === "current"; - let valueHTML; + let valueHTML: string; if (showCurrent) { const currentEid = circuit.entities?.current; const currentState = currentEid ? hass.states[currentEid] : null; @@ -136,8 +149,9 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, // Shedding icon (supports composite: dual-icon or icon+text) const priority = sheddingPriority || "unknown"; - const shedInfo = SHEDDING_PRIORITIES[priority] || SHEDDING_PRIORITIES.unknown; - let sheddingHTML; + const shedInfo: SheddingPriorityDef = SHEDDING_PRIORITIES[priority] ?? + SHEDDING_PRIORITIES.unknown ?? { icon: "mdi:help", color: "#999", label: () => "Unknown" }; + let sheddingHTML: string; if (shedInfo.icon2) { sheddingHTML = ` @@ -216,12 +230,8 @@ export function renderCircuitSlot(uuid, circuit, row, col, layout, _durationMs, /** * Render an empty breaker slot. - * - * @param {number} row - Grid row number. - * @param {string} col - Grid column value (CSS grid-column). - * @returns {string} HTML string for the empty slot. */ -export function renderEmptySlot(row, col) { +export function renderEmptySlot(row: number, col: string): string { return `
diff --git a/src/core/header-renderer.js b/src/core/header-renderer.ts similarity index 81% rename from src/core/header-renderer.js rename to src/core/header-renderer.ts index 8a6bde1..0619e05 100644 --- a/src/core/header-renderer.js +++ b/src/core/header-renderer.ts @@ -1,25 +1,23 @@ import { escapeHtml } from "../helpers/sanitize.js"; import { t } from "../i18n.js"; import { SHEDDING_PRIORITIES } from "../constants.js"; +import type { PanelTopology, CardConfig, SheddingPriorityDef } from "../types.js"; /** * Build the panel header HTML with stats, gear icon, and A/W toggle. - * @param {object} topology - Panel topology from WebSocket - * @param {object} config - Card/panel configuration - * @returns {string} HTML string */ -export function buildHeaderHTML(topology, config) { - const panelName = escapeHtml(topology.device_name || t("header.default_name")); - const serial = escapeHtml(topology.serial || ""); - const firmware = escapeHtml(topology.firmware || ""); - const isAmpsMode = (config.chart_metric || "power") === "current"; +export function buildHeaderHTML(topology: PanelTopology, config: CardConfig): string { + const panelName: string = escapeHtml(topology.device_name || t("header.default_name")); + const serial: string = escapeHtml(topology.serial || ""); + const firmware: string = escapeHtml(topology.firmware || ""); + const isAmpsMode: boolean = (config.chart_metric || "power") === "current"; - const hasSite = !!topology.panel_entities?.site_power; - const hasGrid = !!topology.panel_entities?.dsm_state; - const hasUpstream = !!topology.panel_entities?.current_power; - const hasDownstream = !!topology.panel_entities?.feedthrough_power; - const hasSolar = !!topology.panel_entities?.pv_power; - const hasBattery = !!topology.panel_entities?.battery_level; + const hasSite: boolean = !!topology.panel_entities?.site_power; + const hasGrid: boolean = !!topology.panel_entities?.dsm_state; + const hasUpstream: boolean = !!topology.panel_entities?.current_power; + const hasDownstream: boolean = !!topology.panel_entities?.feedthrough_power; + const hasSolar: boolean = !!topology.panel_entities?.pv_power; + const hasBattery: boolean = !!topology.panel_entities?.battery_level; return `
@@ -121,9 +119,9 @@ export function buildHeaderHTML(topology, config) {
${Object.entries(SHEDDING_PRIORITIES) - .filter(([key]) => key !== "unknown") - .map(([, cfg]) => { - let icons; + .filter(([key]: [string, SheddingPriorityDef]) => key !== "unknown") + .map(([, cfg]: [string, SheddingPriorityDef]) => { + let icons: string; if (cfg.icon2) { icons = ``; } else if (cfg.textLabel) { diff --git a/src/core/history-loader.js b/src/core/history-loader.ts similarity index 66% rename from src/core/history-loader.js rename to src/core/history-loader.ts index c430b5a..b0bc8e1 100644 --- a/src/core/history-loader.js +++ b/src/core/history-loader.ts @@ -1,14 +1,37 @@ import { getHistoryDurationMs, getMaxHistoryPoints, getMinGapMs, deduplicateAndTrim, getHorizonDurationMs } from "../helpers/history.js"; import { getCircuitChartEntity } from "../helpers/chart.js"; import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity } from "../helpers/entity-finder.js"; -import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_KEY_PREFIX } from "../constants.js"; +import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_KEY_PREFIX, STATISTICS_PERIOD_THRESHOLD_HOURS } from "../constants.js"; +import type { HomeAssistant, PanelTopology, CardConfig, HistoryMap, HistoryPoint, SubDeviceEntityRef } from "../types.js"; -async function loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory) { +interface StatisticsEntry { + start: number; + mean: number | null; +} + +interface RawHistoryEntry { + s: string; + lu?: number; + lc?: number; +} + +interface DurationGroup { + entityIds: string[]; + uuidByEntity: Map; +} + +async function loadStatisticsHistory( + hass: HomeAssistant, + entityIds: string[], + uuidByEntity: Map, + durationMs: number, + powerHistory: HistoryMap +): Promise { const startTime = new Date(Date.now() - durationMs).toISOString(); const durationHours = durationMs / (60 * 60 * 1000); - const period = durationHours > 72 ? "hour" : "5minute"; + const period = durationHours > STATISTICS_PERIOD_THRESHOLD_HOURS ? "hour" : "5minute"; - const result = await hass.callWS({ + const result = await hass.callWS>({ type: "recorder/statistics_during_period", start_time: startTime, statistic_ids: entityIds, @@ -20,7 +43,7 @@ async function loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, const uuid = uuidByEntity.get(entityId); if (!uuid || !stats) continue; - const hist = []; + const hist: HistoryPoint[] = []; for (const entry of stats) { const val = entry.mean; if (val == null || !Number.isFinite(val)) continue; @@ -32,15 +55,21 @@ async function loadStatisticsHistory(hass, entityIds, uuidByEntity, durationMs, if (hist.length > 0) { const existing = powerHistory.get(uuid) || []; const merged = [...hist, ...existing]; - merged.sort((a, b) => a.time - b.time); + merged.sort((a: HistoryPoint, b: HistoryPoint) => a.time - b.time); powerHistory.set(uuid, merged); } } } -async function loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHistory) { +async function loadRawHistory( + hass: HomeAssistant, + entityIds: string[], + uuidByEntity: Map, + durationMs: number, + powerHistory: HistoryMap +): Promise { const startTime = new Date(Date.now() - durationMs).toISOString(); - const result = await hass.callWS({ + const result = await hass.callWS>({ type: "history/history_during_period", start_time: startTime, entity_ids: entityIds, @@ -55,7 +84,7 @@ async function loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHi const uuid = uuidByEntity.get(entityId); if (!uuid || !states) continue; - const hist = []; + const hist: HistoryPoint[] = []; for (const entry of states) { const val = parseFloat(entry.s); if (!Number.isFinite(val)) continue; @@ -74,16 +103,13 @@ async function loadRawHistory(hass, entityIds, uuidByEntity, durationMs, powerHi /** * Build the entity ID list for all sub-devices. - * Returns an array of { entityId, key } pairs so callers can record live samples. - * - * @param {object} topology - * @returns {{ entityId: string, key: string, devId: string }[]} + * Returns an array of { entityId, key, devId } so callers can record live samples. */ -export function collectSubDeviceEntityIds(topology) { +export function collectSubDeviceEntityIds(topology: PanelTopology): SubDeviceEntityRef[] { if (!topology.sub_devices) return []; - const results = []; + const results: SubDeviceEntityRef[] = []; for (const [devId, sub] of Object.entries(topology.sub_devices)) { - const eidMap = { power: findSubDevicePowerEntity(sub) }; + const eidMap: Record = { power: findSubDevicePowerEntity(sub) }; if (sub.type === SUB_DEVICE_TYPE_BESS) { eidMap.soc = findBatteryLevelEntity(sub); eidMap.soe = findBatterySoeEntity(sub); @@ -100,60 +126,60 @@ export function collectSubDeviceEntityIds(topology) { /** * Load historical power data from HA recorder into the powerHistory Map. * Supports per-circuit horizons by grouping circuits by their effective duration. - * - * @param {object} hass - * @param {object} topology - * @param {object} config - card config (fallback for duration) - * @param {Map} powerHistory - mutated in place - * @param {Map} [horizonMap] - optional uuid → horizon key map - * @param {Map} [subDeviceHorizonMap] - optional devId → horizon key map */ -export async function loadHistory(hass, topology, config, powerHistory, horizonMap, subDeviceHorizonMap) { +export async function loadHistory( + hass: HomeAssistant, + topology: PanelTopology, + config: CardConfig, + powerHistory: HistoryMap, + horizonMap?: Map, + subDeviceHorizonMap?: Map +): Promise { if (!topology || !hass) return; // Group circuits by effective duration - const groups = new Map(); // durationMs → { entityIds: [], uuidByEntity: Map } + const groups = new Map(); for (const [uuid, circuit] of Object.entries(topology.circuits)) { const eid = getCircuitChartEntity(circuit, config); if (!eid) continue; - let durationMs; + let durationMs: number; if (horizonMap && horizonMap.has(uuid)) { - durationMs = getHorizonDurationMs(horizonMap.get(uuid)); + durationMs = getHorizonDurationMs(horizonMap.get(uuid)!); } else { durationMs = getHistoryDurationMs(config); } if (!groups.has(durationMs)) { - groups.set(durationMs, { entityIds: [], uuidByEntity: new Map() }); + groups.set(durationMs, { entityIds: [], uuidByEntity: new Map() }); } - const group = groups.get(durationMs); + const group = groups.get(durationMs)!; group.entityIds.push(eid); group.uuidByEntity.set(eid, uuid); } // Add sub-device entities grouped by their effective horizon for (const { entityId, key, devId } of collectSubDeviceEntityIds(topology)) { - let durationMs; + let durationMs: number; if (subDeviceHorizonMap && subDeviceHorizonMap.has(devId)) { - durationMs = getHorizonDurationMs(subDeviceHorizonMap.get(devId)); + durationMs = getHorizonDurationMs(subDeviceHorizonMap.get(devId)!); } else { durationMs = getHistoryDurationMs(config); } if (!groups.has(durationMs)) { - groups.set(durationMs, { entityIds: [], uuidByEntity: new Map() }); + groups.set(durationMs, { entityIds: [], uuidByEntity: new Map() }); } - const group = groups.get(durationMs); + const group = groups.get(durationMs)!; group.entityIds.push(entityId); group.uuidByEntity.set(entityId, key); } // Load each group in parallel - const promises = []; + const promises: Promise[] = []; for (const [durationMs, group] of groups) { if (group.entityIds.length === 0) continue; - const useStatistics = durationMs > 2 * 60 * 60 * 1000; + const useStatistics = durationMs > STATISTICS_PERIOD_THRESHOLD_HOURS * 60 * 60 * 1000; if (useStatistics) { promises.push(loadStatisticsHistory(hass, group.entityIds, group.uuidByEntity, durationMs, powerHistory)); } else { diff --git a/src/core/monitoring-status.js b/src/core/monitoring-status.ts similarity index 57% rename from src/core/monitoring-status.js rename to src/core/monitoring-status.ts index bf12b57..d78e99c 100644 --- a/src/core/monitoring-status.js +++ b/src/core/monitoring-status.ts @@ -1,26 +1,26 @@ -// src/core/monitoring-status.js import { INTEGRATION_DOMAIN } from "../constants.js"; import { t } from "../i18n.js"; +import type { HomeAssistant, MonitoringPointInfo, MonitoringStatus } from "../types.js"; const MONITORING_POLL_INTERVAL_MS = 30_000; +interface CallServiceResponse { + response?: MonitoringStatus; +} + /** * Caches monitoring status fetched via the get_monitoring_status service. * Re-fetches at most every 30 seconds. */ export class MonitoringStatusCache { - constructor() { - this._status = null; - this._lastFetch = 0; - this._fetching = false; - } + private _status: MonitoringStatus | null = null; + private _lastFetch: number = 0; + private _fetching: boolean = false; /** * Fetch monitoring status, returning cached data if recent. - * @param {object} hass - Home Assistant instance - * @returns {Promise} Monitoring status or null */ - async fetch(hass, configEntryId) { + async fetch(hass: HomeAssistant, configEntryId?: string | null): Promise { const now = Date.now(); if (this._fetching) return this._status; if (this._status && now - this._lastFetch < MONITORING_POLL_INTERVAL_MS) { @@ -29,16 +29,16 @@ export class MonitoringStatusCache { this._fetching = true; try { - const serviceData = {}; + const serviceData: Record = {}; if (configEntryId) serviceData.config_entry_id = configEntryId; - const resp = await hass.callWS({ + const resp = await hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, service: "get_monitoring_status", service_data: serviceData, return_response: true, }); - this._status = resp?.response || null; + this._status = resp?.response ?? null; this._lastFetch = now; } catch { this._status = null; @@ -49,17 +49,17 @@ export class MonitoringStatusCache { } /** Force the next fetch() call to re-query the backend. */ - invalidate() { + invalidate(): void { this._lastFetch = 0; } - /** @returns {object|null} Last fetched status */ - get status() { + /** Last fetched status. */ + get status(): MonitoringStatus | null { return this._status; } /** Clear cached status (e.g., on config change). */ - clear() { + clear(): void { this._status = null; this._lastFetch = 0; } @@ -67,42 +67,24 @@ export class MonitoringStatusCache { /** * Get monitoring info for a specific circuit entity. - * @param {object|null} status - Full monitoring status - * @param {string} entityId - Circuit entity ID - * @returns {object|null} Circuit monitoring info or null */ -export function getCircuitMonitoringInfo(status, entityId) { +export function getCircuitMonitoringInfo(status: MonitoringStatus | null, entityId: string): MonitoringPointInfo | null { if (!status?.circuits) return null; - return status.circuits[entityId] || null; -} - -/** - * Get monitoring info for a mains leg entity. - * @param {object|null} status - Full monitoring status - * @param {string} entityId - Mains entity ID - * @returns {object|null} Mains monitoring info or null - */ -export function getMainsMonitoringInfo(status, entityId) { - if (!status?.mains) return null; - return status.mains[entityId] || null; + return status.circuits[entityId] ?? null; } /** * Check if a monitored point has custom (non-global) overrides. - * @param {object|null} monitoringInfo - Per-circuit monitoring info - * @returns {boolean} */ -export function hasCustomOverrides(monitoringInfo) { +export function hasCustomOverrides(monitoringInfo: MonitoringPointInfo | null): boolean { if (!monitoringInfo) return false; return monitoringInfo.continuous_threshold_pct !== undefined; } /** * Get CSS class for utilization level. - * @param {object|null} monitoringInfo - Per-circuit monitoring info - * @returns {string} CSS class name or empty string */ -export function getUtilizationClass(monitoringInfo) { +export function getUtilizationClass(monitoringInfo: MonitoringPointInfo | null): string { if (!monitoringInfo?.utilization_pct) return ""; const pct = monitoringInfo.utilization_pct; if (pct >= 100) return "utilization-alert"; @@ -112,28 +94,24 @@ export function getUtilizationClass(monitoringInfo) { /** * Check if a circuit currently has an active alert. - * @param {object|null} monitoringInfo - Per-circuit monitoring info - * @returns {boolean} */ -export function isAlertActive(monitoringInfo) { +export function isAlertActive(monitoringInfo: MonitoringPointInfo | null): boolean { if (!monitoringInfo) return false; return monitoringInfo.over_threshold_since != null; } /** * Build HTML for the monitoring summary bar. - * @param {object|null} status - Full monitoring status from get_monitoring_status - * @returns {string} HTML string (empty if monitoring disabled) */ -export function buildMonitoringSummaryHTML(status) { +export function buildMonitoringSummaryHTML(status: MonitoringStatus | null): string { if (!status) return ""; - const circuits = Object.values(status.circuits || {}); - const mains = Object.values(status.mains || {}); - const all = [...circuits, ...mains]; + const circuits: MonitoringPointInfo[] = Object.values(status.circuits ?? {}); + const mains: MonitoringPointInfo[] = Object.values(status.mains ?? {}); + const all: MonitoringPointInfo[] = [...circuits, ...mains]; - const warnings = all.filter(p => p.utilization_pct >= 80 && p.utilization_pct < 100).length; - const alerts = all.filter(p => p.utilization_pct >= 100).length; + const warnings = all.filter(p => p.utilization_pct !== undefined && p.utilization_pct >= 80 && p.utilization_pct < 100).length; + const alerts = all.filter(p => p.utilization_pct !== undefined && p.utilization_pct >= 100).length; const overrides = all.filter(p => p.has_override).length; return ` diff --git a/src/core/side-panel.js b/src/core/side-panel.ts similarity index 79% rename from src/core/side-panel.js rename to src/core/side-panel.ts index 6a50ddc..68cfea7 100644 --- a/src/core/side-panel.js +++ b/src/core/side-panel.ts @@ -1,11 +1,55 @@ -// src/core/side-panel.js +// src/core/side-panel.ts import { escapeHtml } from "../helpers/sanitize.js"; -import { INTEGRATION_DOMAIN, SHEDDING_PRIORITIES, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; +import { INTEGRATION_DOMAIN, SHEDDING_PRIORITIES, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON, ERROR_DISPLAY_MS, INPUT_DEBOUNCE_MS } from "../constants.js"; import { t } from "../i18n.js"; +import type { HomeAssistant, PanelTopology, GraphSettings, CircuitEntities, CircuitGraphOverride, MonitoringPointInfo } from "../types.js"; -const DEBOUNCE_MS = 500; +const PRIORITY_OPTIONS: string[] = Object.keys(SHEDDING_PRIORITIES).filter(k => k !== "unknown"); -const PRIORITY_OPTIONS = Object.keys(SHEDDING_PRIORITIES).filter(k => k !== "unknown"); +// ── Interfaces for config shapes passed to open() ──────────────────────── + +interface GraphHorizonInfo extends CircuitGraphOverride { + globalHorizon: string; +} + +interface PanelModeConfig { + panelMode: true; + subDeviceMode?: undefined; + topology: PanelTopology; + graphSettings: GraphSettings | null; +} + +interface CircuitModeConfig { + panelMode?: undefined; + subDeviceMode?: undefined; + uuid: string; + name: string; + tabs: number[]; + breaker_rating_a?: number; + voltage?: number; + entities: CircuitEntities; + is_user_controllable?: boolean; + always_on?: boolean; + monitoringInfo: MonitoringPointInfo | null; + graphHorizonInfo: GraphHorizonInfo; +} + +interface SubDeviceModeConfig { + panelMode?: undefined; + subDeviceMode: true; + subDeviceId: string; + name: string; + deviceType: string; + graphHorizonInfo: GraphHorizonInfo; +} + +type SidePanelConfig = PanelModeConfig | CircuitModeConfig | SubDeviceModeConfig; + +// ── Custom element interface for ha-switch ─────────────────────────────── + +interface HaSwitchElement extends HTMLElement { + checked: boolean; +} // ── Styles ──────────────────────────────────────────────────────────────── @@ -209,6 +253,10 @@ const STYLES = ` // ── Component ───────────────────────────────────────────────────────────── class SpanSidePanel extends HTMLElement { + private _hass: HomeAssistant | null; + private _config: SidePanelConfig | null; + private _debounceTimers: Record>; + constructor() { super(); this.attachShadow({ mode: "open" }); @@ -217,18 +265,18 @@ class SpanSidePanel extends HTMLElement { this._debounceTimers = {}; } - set hass(val) { + set hass(val: HomeAssistant | null) { this._hass = val; if (this.hasAttribute("open") && this._config) { this._updateLiveState(); } } - get hass() { + get hass(): HomeAssistant | null { return this._hass; } - open(config) { + open(config: SidePanelConfig): void { this._config = config; this._render(); // Force reflow before adding attribute so the transition animates @@ -236,7 +284,7 @@ class SpanSidePanel extends HTMLElement { this.setAttribute("open", ""); } - close() { + close(): void { this.removeAttribute("open"); this._config = null; this.dispatchEvent(new CustomEvent("side-panel-closed", { bubbles: true, composed: true })); @@ -244,11 +292,12 @@ class SpanSidePanel extends HTMLElement { // ── Rendering ───────────────────────────────────────────────────────── - _render() { + private _render(): void { const cfg = this._config; if (!cfg) return; const shadow = this.shadowRoot; + if (!shadow) return; shadow.innerHTML = ""; const style = document.createElement("style"); @@ -273,8 +322,8 @@ class SpanSidePanel extends HTMLElement { } } - _renderPanelMode(panel) { - const cfg = this._config; + private _renderPanelMode(panel: HTMLDivElement): void { + const cfg = this._config as PanelModeConfig; const header = this._createHeader(t("sidepanel.graph_settings"), t("sidepanel.global_defaults")); panel.appendChild(header); @@ -322,7 +371,7 @@ class SpanSidePanel extends HTMLElement { .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${err.message ?? err}`)); }); globalRow.appendChild(globalSelect); globalSection.appendChild(globalRow); @@ -350,7 +399,7 @@ class SpanSidePanel extends HTMLElement { nameLabel.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;"; row.appendChild(nameLabel); - const circuitData = circuitSettings[uuid] || {}; + const circuitData = circuitSettings[uuid] || { horizon: globalHorizon, has_override: false }; const effectiveHorizon = circuitData.has_override ? circuitData.horizon : globalHorizon; const select = document.createElement("select"); @@ -363,7 +412,7 @@ class SpanSidePanel extends HTMLElement { select.appendChild(opt); } select.addEventListener("change", () => { - this._debounce(`circuit-${uuid}`, DEBOUNCE_MS, () => { + this._debounce(`circuit-${uuid}`, INPUT_DEBOUNCE_MS, () => { this._callDomainService("set_circuit_graph_horizon", { circuit_id: uuid, horizon: select.value, @@ -371,7 +420,7 @@ class SpanSidePanel extends HTMLElement { .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${err.message ?? err}`)); }); }); row.appendChild(select); @@ -397,7 +446,7 @@ class SpanSidePanel extends HTMLElement { resetBtn.remove(); this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${err.message ?? err}`)); }); row.appendChild(resetBtn); } @@ -431,7 +480,7 @@ class SpanSidePanel extends HTMLElement { nameLabel.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;"; row.appendChild(nameLabel); - const subDevData = subDeviceSettings[devId] || {}; + const subDevData = subDeviceSettings[devId] || { horizon: globalHorizon, has_override: false }; const effectiveHorizon = subDevData.has_override ? subDevData.horizon : globalHorizon; const select = document.createElement("select"); @@ -444,7 +493,7 @@ class SpanSidePanel extends HTMLElement { select.appendChild(opt); } select.addEventListener("change", () => { - this._debounce(`subdev-${devId}`, DEBOUNCE_MS, () => { + this._debounce(`subdev-${devId}`, INPUT_DEBOUNCE_MS, () => { this._callDomainService("set_subdevice_graph_horizon", { subdevice_id: devId, horizon: select.value, @@ -452,7 +501,7 @@ class SpanSidePanel extends HTMLElement { .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${err.message ?? err}`)); }); }); row.appendChild(select); @@ -478,7 +527,7 @@ class SpanSidePanel extends HTMLElement { resetBtn.remove(); this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${err.message ?? err}`)); }); row.appendChild(resetBtn); } @@ -492,7 +541,7 @@ class SpanSidePanel extends HTMLElement { panel.appendChild(body); } - _renderCircuitMode(panel, cfg) { + private _renderCircuitMode(panel: HTMLDivElement, cfg: CircuitModeConfig): void { const subtitle = `${escapeHtml(String(cfg.breaker_rating_a))}A \u00b7 ${escapeHtml(String(cfg.voltage))}V \u00b7 Tabs [${escapeHtml(String(cfg.tabs))}]`; const header = this._createHeader(escapeHtml(cfg.name), subtitle); panel.appendChild(header); @@ -513,7 +562,7 @@ class SpanSidePanel extends HTMLElement { this._renderMonitoringSection(body, cfg); } - _renderSubDeviceMode(panel, cfg) { + private _renderSubDeviceMode(panel: HTMLDivElement, cfg: SubDeviceModeConfig): void { const header = this._createHeader(escapeHtml(cfg.name), escapeHtml(cfg.deviceType)); panel.appendChild(header); @@ -530,7 +579,7 @@ class SpanSidePanel extends HTMLElement { this._renderSubDeviceHorizonSection(body, cfg); } - _renderSubDeviceHorizonSection(body, cfg) { + private _renderSubDeviceHorizonSection(body: HTMLDivElement, cfg: SubDeviceModeConfig): void { const section = document.createElement("div"); section.className = "section"; @@ -547,15 +596,15 @@ class SpanSidePanel extends HTMLElement { const bar = document.createElement("div"); bar.className = "horizon-bar"; - const segments = [{ key: "global", label: t("sidepanel.global") }]; + const segments: { key: string; label: string }[] = [{ key: "global", label: t("sidepanel.global") }]; for (const key of Object.keys(GRAPH_HORIZONS)) { segments.push({ key, label: key }); } const activeKey = hasOverride ? currentHorizon : "global"; - const updateSegmentStates = newActiveKey => { - for (const btn of bar.querySelectorAll(".horizon-segment")) { + const updateSegmentStates = (newActiveKey: string): void => { + for (const btn of bar.querySelectorAll(".horizon-segment")) { const key = btn.dataset.horizon; btn.classList.toggle("active", key === newActiveKey); btn.classList.toggle("referenced", newActiveKey === "global" && key === globalHorizon); @@ -581,7 +630,7 @@ class SpanSidePanel extends HTMLElement { .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${err.message ?? err}`)); } else { updateSegmentStates(key); this._callDomainService("set_subdevice_graph_horizon", { @@ -591,7 +640,7 @@ class SpanSidePanel extends HTMLElement { .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); } }); @@ -602,12 +651,14 @@ class SpanSidePanel extends HTMLElement { body.appendChild(section); } - _createHeader(title, subtitle) { + private _createHeader(title: string, subtitle: string): HTMLDivElement { const header = document.createElement("div"); header.className = "panel-header"; const titleWrap = document.createElement("div"); - titleWrap.innerHTML = `
${title}
` + (subtitle ? `
${subtitle}
` : ""); + const safeTitle = escapeHtml(title); + const safeSubtitle = escapeHtml(subtitle); + titleWrap.innerHTML = `
${safeTitle}
` + (safeSubtitle ? `
${safeSubtitle}
` : ""); const closeBtn = document.createElement("button"); closeBtn.className = "close-btn"; @@ -621,12 +672,12 @@ class SpanSidePanel extends HTMLElement { // ── Relay section ─────────────────────────────────────────────────── - _renderRelaySection(body, cfg) { + private _renderRelaySection(body: HTMLDivElement, cfg: CircuitModeConfig): void { if (cfg.is_user_controllable === false || !cfg.entities?.switch) return; const section = document.createElement("div"); section.className = "section"; - section.innerHTML = ``; + section.innerHTML = ``; const row = document.createElement("div"); row.className = "field-row"; @@ -635,7 +686,7 @@ class SpanSidePanel extends HTMLElement { label.className = "field-label"; label.textContent = t("sidepanel.breaker"); - const toggle = document.createElement("ha-switch"); + const toggle = document.createElement("ha-switch") as HaSwitchElement; toggle.dataset.role = "relay-toggle"; const entityId = cfg.entities.switch; const currentState = this._hass?.states?.[entityId]?.state; @@ -645,7 +696,7 @@ class SpanSidePanel extends HTMLElement { toggle.addEventListener("change", () => { const isOn = toggle.hasAttribute("checked") || toggle.checked; - this._callService("switch", isOn ? "turn_on" : "turn_off", { entity_id: entityId }).catch(err => + this._callService("switch", isOn ? "turn_on" : "turn_off", { entity_id: entityId }).catch((err: Error) => this._showError(`${t("sidepanel.relay_failed")} ${err.message ?? err}`) ); }); @@ -658,12 +709,12 @@ class SpanSidePanel extends HTMLElement { // ── Shedding section ──────────────────────────────────────────────── - _renderSheddingSection(body, cfg) { + private _renderSheddingSection(body: HTMLDivElement, cfg: CircuitModeConfig): void { if (!cfg.entities?.select) return; const section = document.createElement("div"); section.className = "section"; - section.innerHTML = ``; + section.innerHTML = ``; const row = document.createElement("div"); row.className = "field-row"; @@ -678,9 +729,11 @@ class SpanSidePanel extends HTMLElement { const currentPriority = this._hass?.states?.[entityId]?.state || ""; for (const key of PRIORITY_OPTIONS) { + const priority = SHEDDING_PRIORITIES[key]; + if (!priority) continue; const opt = document.createElement("option"); opt.value = key; - opt.textContent = SHEDDING_PRIORITIES[key].label(); + opt.textContent = priority.label(); if (key === currentPriority) opt.selected = true; selectEl.appendChild(opt); } @@ -689,7 +742,7 @@ class SpanSidePanel extends HTMLElement { this._callService("select", "select_option", { entity_id: entityId, option: selectEl.value, - }).catch(err => this._showError(`${t("sidepanel.shedding_failed")} ${err.message ?? err}`)); + }).catch((err: Error) => this._showError(`${t("sidepanel.shedding_failed")} ${err.message ?? err}`)); }); row.appendChild(label); @@ -700,7 +753,7 @@ class SpanSidePanel extends HTMLElement { // ── Graph horizon section ────────────────────────────────────────── - _renderGraphHorizonSection(body, cfg) { + private _renderGraphHorizonSection(body: HTMLDivElement, cfg: CircuitModeConfig): void { const section = document.createElement("div"); section.className = "section"; @@ -718,15 +771,15 @@ class SpanSidePanel extends HTMLElement { const bar = document.createElement("div"); bar.className = "horizon-bar"; - const segments = [{ key: "global", label: t("sidepanel.global") }]; + const segments: { key: string; label: string }[] = [{ key: "global", label: t("sidepanel.global") }]; for (const key of Object.keys(GRAPH_HORIZONS)) { segments.push({ key, label: key }); } const activeKey = hasOverride ? currentHorizon : "global"; - const updateSegmentStates = newActiveKey => { - for (const btn of bar.querySelectorAll(".horizon-segment")) { + const updateSegmentStates = (newActiveKey: string): void => { + for (const btn of bar.querySelectorAll(".horizon-segment")) { const key = btn.dataset.horizon; btn.classList.toggle("active", key === newActiveKey); btn.classList.toggle("referenced", newActiveKey === "global" && key === globalHorizon); @@ -752,7 +805,7 @@ class SpanSidePanel extends HTMLElement { .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${t("sidepanel.clear_graph_horizon_failed")} ${err.message ?? err}`)); } else { updateSegmentStates(key); this._callDomainService("set_circuit_graph_horizon", { @@ -762,7 +815,7 @@ class SpanSidePanel extends HTMLElement { .then(() => { this.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); }) - .catch(err => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); + .catch((err: Error) => this._showError(`${t("sidepanel.graph_horizon_failed")} ${err.message ?? err}`)); } }); @@ -775,7 +828,7 @@ class SpanSidePanel extends HTMLElement { // ── Monitoring section ────────────────────────────────────────────── - _renderMonitoringSection(body, cfg) { + private _renderMonitoringSection(body: HTMLDivElement, cfg: CircuitModeConfig): void { const section = document.createElement("div"); section.className = "section"; @@ -787,7 +840,7 @@ class SpanSidePanel extends HTMLElement { sectionLabel.textContent = t("sidepanel.monitoring"); sectionLabel.style.margin = "0"; - const enableToggle = document.createElement("ha-switch"); + const enableToggle = document.createElement("ha-switch") as HaSwitchElement; enableToggle.dataset.role = "monitoring-toggle"; const info = cfg.monitoringInfo; @@ -811,8 +864,8 @@ class SpanSidePanel extends HTMLElement { const radioGroup = document.createElement("div"); radioGroup.className = "radio-group"; radioGroup.innerHTML = ` - - + + `; detailsWrap.appendChild(radioGroup); @@ -840,18 +893,18 @@ class SpanSidePanel extends HTMLElement { this._callDomainService("set_circuit_threshold", { circuit_id: entityId, monitoring_enabled: checked, - }).catch(err => this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${err.message ?? err}`)); + }).catch((err: Error) => this._showError(`${t("sidepanel.monitoring_toggle_failed")} ${err.message ?? err}`)); }); // Event: radio change - const radios = radioGroup.querySelectorAll('input[type="radio"]'); + const radios = radioGroup.querySelectorAll('input[type="radio"]'); for (const radio of radios) { radio.addEventListener("change", () => { const isCustom = radio.value === "custom" && radio.checked; thresholdsWrap.style.display = isCustom ? "block" : "none"; if (!isCustom && radio.checked) { const entityId = cfg.entities?.power || cfg.uuid; - this._callDomainService("clear_circuit_threshold", { circuit_id: entityId }).catch(err => + this._callDomainService("clear_circuit_threshold", { circuit_id: entityId }).catch((err: Error) => this._showError(`${t("sidepanel.clear_monitoring_failed")} ${err.message ?? err}`) ); } @@ -861,7 +914,7 @@ class SpanSidePanel extends HTMLElement { body.appendChild(section); } - _createThresholdRow(label, key, value, cfg) { + private _createThresholdRow(label: string, key: string, value: number, cfg: CircuitModeConfig): HTMLDivElement { const row = document.createElement("div"); row.className = "field-row"; @@ -877,11 +930,13 @@ class SpanSidePanel extends HTMLElement { input.dataset.role = `threshold-${key}`; input.addEventListener("input", () => { - this._debounce(`threshold-${key}`, DEBOUNCE_MS, () => { - const continuous = this.shadowRoot.querySelector('[data-role="threshold-continuous"]'); - const spike = this.shadowRoot.querySelector('[data-role="threshold-spike"]'); - const windowM = this.shadowRoot.querySelector('[data-role="threshold-window-m"]'); - const cooldownM = this.shadowRoot.querySelector('[data-role="threshold-cooldown-m"]'); + this._debounce(`threshold-${key}`, INPUT_DEBOUNCE_MS, () => { + const shadow = this.shadowRoot; + if (!shadow) return; + const continuous = shadow.querySelector('[data-role="threshold-continuous"]'); + const spike = shadow.querySelector('[data-role="threshold-spike"]'); + const windowM = shadow.querySelector('[data-role="threshold-window-m"]'); + const cooldownM = shadow.querySelector('[data-role="threshold-cooldown-m"]'); const entityId = cfg.entities?.power || cfg.uuid; this._callDomainService("set_circuit_threshold", { circuit_id: entityId, @@ -889,7 +944,7 @@ class SpanSidePanel extends HTMLElement { spike_threshold_pct: spike ? Number(spike.value) : undefined, window_duration_m: windowM ? Number(windowM.value) : undefined, cooldown_duration_m: cooldownM ? Number(cooldownM.value) : undefined, - }).catch(err => this._showError(`${t("sidepanel.save_threshold_failed")} ${err.message ?? err}`)); + }).catch((err: Error) => this._showError(`${t("sidepanel.save_threshold_failed")} ${err.message ?? err}`)); }); }); @@ -898,7 +953,16 @@ class SpanSidePanel extends HTMLElement { return row; } - _createDurationRow(label, key, value, min, max, unit, cfg, readOnly = false) { + private _createDurationRow( + label: string, + key: string, + value: number, + min: number, + max: number, + unit: string, + cfg: CircuitModeConfig, + readOnly: boolean = false + ): HTMLDivElement { const row = document.createElement("div"); row.className = "field-row"; @@ -926,16 +990,18 @@ class SpanSidePanel extends HTMLElement { if (!readOnly) { input.addEventListener("input", () => { - this._debounce(`threshold-${key}`, DEBOUNCE_MS, () => { - const continuous = this.shadowRoot.querySelector('[data-role="threshold-continuous"]'); - const spike = this.shadowRoot.querySelector('[data-role="threshold-spike"]'); - const windowM = this.shadowRoot.querySelector('[data-role="threshold-window-m"]'); + this._debounce(`threshold-${key}`, INPUT_DEBOUNCE_MS, () => { + const shadow = this.shadowRoot; + if (!shadow) return; + const continuous = shadow.querySelector('[data-role="threshold-continuous"]'); + const spike = shadow.querySelector('[data-role="threshold-spike"]'); + const windowM = shadow.querySelector('[data-role="threshold-window-m"]'); this._callDomainService("set_circuit_threshold", { circuit_id: cfg.uuid, continuous_threshold_pct: continuous ? Number(continuous.value) : undefined, spike_threshold_pct: spike ? Number(spike.value) : undefined, window_duration_m: windowM ? Number(windowM.value) : undefined, - }).catch(err => this._showError(`${t("sidepanel.save_threshold_failed")} ${err.message ?? err}`)); + }).catch((err: Error) => this._showError(`${t("sidepanel.save_threshold_failed")} ${err.message ?? err}`)); }); }); } @@ -947,13 +1013,16 @@ class SpanSidePanel extends HTMLElement { // ── Live state updates ────────────────────────────────────────────── - _updateLiveState() { + private _updateLiveState(): void { if (!this._config || this._config.panelMode) return; const cfg = this._config; + // Sub-device mode has no live-updating fields + if (cfg.subDeviceMode) return; + // Update relay toggle if (cfg.entities?.switch) { - const toggle = this.shadowRoot.querySelector('[data-role="relay-toggle"]'); + const toggle = this.shadowRoot?.querySelector('[data-role="relay-toggle"]'); if (toggle) { const currentState = this._hass?.states?.[cfg.entities.switch]?.state; if (currentState === "on") { @@ -966,7 +1035,7 @@ class SpanSidePanel extends HTMLElement { // Update shedding select if (cfg.entities?.select) { - const selectEl = this.shadowRoot.querySelector('[data-role="shedding-select"]'); + const selectEl = this.shadowRoot?.querySelector('[data-role="shedding-select"]'); if (selectEl) { const currentPriority = this._hass?.states?.[cfg.entities.select]?.state || ""; selectEl.value = currentPriority; @@ -976,31 +1045,38 @@ class SpanSidePanel extends HTMLElement { // ── Service calls ─────────────────────────────────────────────────── - _callService(domain, service, data) { + private _callService(domain: string, service: string, data: Record): Promise { if (!this._hass) return Promise.resolve(); return Promise.resolve(this._hass.callService(domain, service, data)); } - _callDomainService(service, data) { - return this._callService(INTEGRATION_DOMAIN, service, data); + private _callDomainService(service: string, data: Record): Promise { + if (!this._hass) return Promise.resolve(); + return this._hass.callWS({ + type: "call_service", + domain: INTEGRATION_DOMAIN, + service, + service_data: data, + return_response: true, + }); } // ── Error display ─────────────────────────────────────────────────── - _showError(message) { - const el = this.shadowRoot.getElementById("error-msg"); + private _showError(message: string): void { + const el = this.shadowRoot?.getElementById("error-msg"); if (el) { el.textContent = message; el.style.display = "block"; setTimeout(() => { el.style.display = "none"; - }, 5000); + }, ERROR_DISPLAY_MS); } } // ── Debounce ──────────────────────────────────────────────────────── - _debounce(key, ms, fn) { + private _debounce(key: string, ms: number, fn: () => void): void { if (this._debounceTimers[key]) { clearTimeout(this._debounceTimers[key]); } diff --git a/src/core/sub-device-renderer.js b/src/core/sub-device-renderer.ts similarity index 59% rename from src/core/sub-device-renderer.js rename to src/core/sub-device-renderer.ts index 00cc1d7..1cac881 100644 --- a/src/core/sub-device-renderer.js +++ b/src/core/sub-device-renderer.ts @@ -3,23 +3,24 @@ import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; import { t } from "../i18n.js"; import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity, findBatteryCapacityEntity } from "../helpers/entity-finder.js"; import { SUB_DEVICE_TYPE_BESS, SUB_DEVICE_TYPE_EVSE, SUB_DEVICE_KEY_PREFIX } from "../constants.js"; +import type { PanelTopology, HomeAssistant, CardConfig, SubDevice } from "../types.js"; + +interface BessChartDef { + key: string; + title: string; + available: boolean; +} /** * Build the HTML for all sub-devices (BESS, EVSE, etc.) in the topology. - * - * @param {object} topology - Discovered panel topology - * @param {object} hass - Home Assistant object - * @param {object} config - Card configuration - * @param {number} _durationMs - History duration in milliseconds (reserved) - * @returns {string} HTML string */ -export function buildSubDevicesHTML(topology, hass, config, _durationMs) { - const showBattery = config.show_battery !== false; - const showEvse = config.show_evse !== false; +export function buildSubDevicesHTML(topology: PanelTopology, hass: HomeAssistant, config: CardConfig): string { + const showBattery: boolean = config.show_battery !== false; + const showEvse: boolean = config.show_evse !== false; if (!topology.sub_devices) return ""; - const entries = Object.entries(topology.sub_devices).filter(([, sub]) => { + const entries: [string, SubDevice][] = Object.entries(topology.sub_devices).filter(([, sub]) => { if (sub.type === SUB_DEVICE_TYPE_BESS && !showBattery) return false; if (sub.type === SUB_DEVICE_TYPE_EVSE && !showEvse) return false; return true; @@ -27,26 +28,26 @@ export function buildSubDevicesHTML(topology, hass, config, _durationMs) { if (entries.length === 0) return ""; - const evseCount = entries.filter(([, sub]) => sub.type === SUB_DEVICE_TYPE_EVSE).length; + const evseCount: number = entries.filter(([, sub]) => sub.type === SUB_DEVICE_TYPE_EVSE).length; let evseIndex = 0; let subDevHTML = ""; for (const [devId, sub] of entries) { - const label = + const label: string = sub.type === SUB_DEVICE_TYPE_EVSE ? t("subdevice.ev_charger") : sub.type === SUB_DEVICE_TYPE_BESS ? t("subdevice.battery") : t("subdevice.fallback"); - const powerEid = findSubDevicePowerEntity(sub); - const powerState = powerEid ? hass.states[powerEid] : null; - const powerW = powerState ? parseFloat(powerState.state) || 0 : 0; + const powerEid: string | null = findSubDevicePowerEntity(sub); + const powerState = powerEid ? hass.states[powerEid] : undefined; + const powerW: number = powerState ? parseFloat(powerState.state) || 0 : 0; - const isBess = sub.type === SUB_DEVICE_TYPE_BESS; - const isEvse = sub.type === SUB_DEVICE_TYPE_EVSE; - const battLevelEid = isBess ? findBatteryLevelEntity(sub) : null; - const battSoeEid = isBess ? findBatterySoeEntity(sub) : null; - const battCapEid = isBess ? findBatteryCapacityEntity(sub) : null; + const isBess: boolean = sub.type === SUB_DEVICE_TYPE_BESS; + const isEvse: boolean = sub.type === SUB_DEVICE_TYPE_EVSE; + const battLevelEid: string | null = isBess ? findBatteryLevelEntity(sub) : null; + const battSoeEid: string | null = isBess ? findBatterySoeEntity(sub) : null; + const battCapEid: string | null = isBess ? findBatteryCapacityEntity(sub) : null; - const hideEids = new Set([powerEid, battLevelEid, battSoeEid, battCapEid].filter(Boolean)); - const entHTML = buildSubEntityHTML(sub, hass, config, hideEids); - const chartsHTML = buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid); + const hideEids: Set = new Set([powerEid, battLevelEid, battSoeEid, battCapEid].filter((eid): eid is string => eid !== null)); + const entHTML: string = buildSubEntityHTML(sub, hass, config, hideEids); + const chartsHTML: string = buildSubDeviceChartsHTML(devId, sub, isBess, powerEid, battLevelEid, battSoeEid); // EVSE: span full row if it's the odd one out (last on its row alone) let spanClass = ""; @@ -79,15 +80,9 @@ export function buildSubDevicesHTML(topology, hass, config, _durationMs) { /** * Build the HTML for the visible entities of a single sub-device. - * - * @param {object} sub - Sub-device object from topology - * @param {object} hass - Home Assistant object - * @param {object} config - Card configuration - * @param {Set} hideEids - Entity IDs to suppress (already shown elsewhere) - * @returns {string} HTML string */ -export function buildSubEntityHTML(sub, hass, config, hideEids) { - const visibleEnts = config.visible_sub_entities || {}; +export function buildSubEntityHTML(sub: SubDevice, hass: HomeAssistant, config: CardConfig, hideEids: Set): string { + const visibleEnts: Record = config.visible_sub_entities || {}; let entHTML = ""; if (!sub.entities) return entHTML; @@ -96,20 +91,20 @@ export function buildSubEntityHTML(sub, hass, config, hideEids) { if (visibleEnts[entityId] !== true) continue; const state = hass.states[entityId]; if (!state) continue; - let name = info.original_name || state.attributes.friendly_name || entityId; - const devName = sub.name || ""; + let name: string = info.original_name || (state.attributes.friendly_name as string) || entityId; + const devName: string = sub.name || ""; if (name.startsWith(devName + " ")) name = name.slice(devName.length + 1); - let displayValue; + let displayValue: string; if (hass.formatEntityState) { displayValue = hass.formatEntityState(state); } else { displayValue = state.state; - const unit = state.attributes.unit_of_measurement || ""; + const unit = (state.attributes.unit_of_measurement as string) || ""; if (unit) displayValue += " " + unit; } - const rawUnit = state.attributes.unit_of_measurement || ""; + const rawUnit = (state.attributes.unit_of_measurement as string) || ""; if (rawUnit === "Wh") { - const wh = parseFloat(state.state); + const wh: number = parseFloat(state.state); if (!isNaN(wh)) displayValue = (wh / 1000).toFixed(1) + " kWh"; } entHTML += ` @@ -124,28 +119,27 @@ export function buildSubEntityHTML(sub, hass, config, hideEids) { /** * Build the chart container HTML for a sub-device. - * - * @param {string} devId - Sub-device key - * @param {object} sub - Sub-device object from topology (unused, reserved for future use) - * @param {boolean} isBess - Whether this sub-device is a battery - * @param {string|null} powerEid - Power entity ID, if available - * @param {string|null} battLevelEid - Battery level entity ID, if available - * @param {string|null} battSoeEid - Battery SoE entity ID, if available - * @returns {string} HTML string */ -export function buildSubDeviceChartsHTML(devId, _sub, isBess, powerEid, battLevelEid, battSoeEid) { +export function buildSubDeviceChartsHTML( + devId: string, + _sub: SubDevice, + isBess: boolean, + powerEid: string | null, + battLevelEid: string | null, + battSoeEid: string | null +): string { if (isBess) { - const bessCharts = [ + const bessCharts: BessChartDef[] = [ { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soc`, title: t("subdevice.soc"), available: !!battLevelEid }, { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_soe`, title: t("subdevice.soe"), available: !!battSoeEid }, { key: `${SUB_DEVICE_KEY_PREFIX}${devId}_power`, title: t("subdevice.power"), available: !!powerEid }, - ].filter(c => c.available); + ].filter((c): c is BessChartDef => c.available); return `
${bessCharts .map( - c => ` + (c: BessChartDef) => `
${escapeHtml(c.title)}
diff --git a/src/editor/span-panel-card-editor.js b/src/editor/span-panel-card-editor.ts similarity index 73% rename from src/editor/span-panel-card-editor.js rename to src/editor/span-panel-card-editor.ts index 284d487..95681f6 100644 --- a/src/editor/span-panel-card-editor.js +++ b/src/editor/span-panel-card-editor.ts @@ -1,22 +1,41 @@ import { CHART_METRICS, DEFAULT_CHART_METRIC, INTEGRATION_DOMAIN } from "../constants.js"; import { t } from "../i18n.js"; +import type { HomeAssistant, CardConfig, PanelTopology, SubDevice, SubDeviceEntityInfo, ChartMetricDef } from "../types.js"; -export class SpanPanelCardEditor extends HTMLElement { - constructor() { - super(); - this._config = {}; - this._hass = null; - this._panels = null; - this._availableRoles = null; - this._built = false; - } +interface PanelOption { + device_id: string; + label: string; +} - setConfig(config) { +interface DeviceRegistryEntry { + id: string; + name?: string; + name_by_user?: string; + identifiers?: [string, string][]; + via_device_id?: string | null; +} + +export class SpanPanelCardEditor extends HTMLElement { + private _config: CardConfig = {}; + private _hass: HomeAssistant | null = null; + private _panels: PanelOption[] | null = null; + private _availableRoles: Set | null = null; + private _built = false; + + private _panelSelect: HTMLSelectElement | null = null; + private _daysInput: HTMLInputElement | null = null; + private _hoursInput: HTMLInputElement | null = null; + private _minsInput: HTMLInputElement | null = null; + private _metricSelect: HTMLSelectElement | null = null; + private _checkboxes: Record = {}; + private _entityContainers: Record = {}; + + setConfig(config: CardConfig): void { this._config = { ...config }; this._updateControls(); } - set hass(hass) { + set hass(hass: HomeAssistant) { this._hass = hass; if (!this._panels) { this._discoverPanels(); @@ -25,20 +44,20 @@ export class SpanPanelCardEditor extends HTMLElement { } } - async _discoverPanels() { + private async _discoverPanels(): Promise { if (!this._hass) return; - const devices = await this._hass.callWS({ type: "config/device_registry/list" }); + const devices = await this._hass.callWS({ type: "config/device_registry/list" }); this._panels = devices - .filter(d => (d.identifiers || []).some(pair => pair[0] === INTEGRATION_DOMAIN) && !d.via_device_id) + .filter(d => (d.identifiers ?? []).some(pair => pair[0] === INTEGRATION_DOMAIN) && !d.via_device_id) .map(d => { - const serial = (d.identifiers || []).find(p => p[0] === INTEGRATION_DOMAIN)?.[1] || ""; - const name = d.name_by_user || d.name || t("editor.panel_label"); + const serial = (d.identifiers ?? []).find(p => p[0] === INTEGRATION_DOMAIN)?.[1] ?? ""; + const name = d.name_by_user ?? d.name ?? t("editor.panel_label"); return { device_id: d.id, label: `${name} (${serial})` }; }); this._buildEditor(); } - _buildEditor() { + private _buildEditor(): void { this.innerHTML = ""; this._built = true; @@ -73,7 +92,7 @@ export class SpanPanelCardEditor extends HTMLElement { } } - _buildPanelSelector(wrapper, fieldStyle, labelStyle, groupStyle) { + private _buildPanelSelector(wrapper: HTMLElement, fieldStyle: string, labelStyle: string, groupStyle: string): void { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); @@ -109,7 +128,7 @@ export class SpanPanelCardEditor extends HTMLElement { this._panelSelect = select; } - _buildTimeWindow(wrapper, fieldStyle, labelStyle, groupStyle) { + private _buildTimeWindow(wrapper: HTMLElement, fieldStyle: string, labelStyle: string, groupStyle: string): void { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); @@ -122,7 +141,7 @@ export class SpanPanelCardEditor extends HTMLElement { const inputStyle = fieldStyle + "width: 70px; cursor: text;"; const spanStyle = "font-size: 0.9em; color: var(--secondary-text-color);"; - const createTimeField = (value, min, max, unitLabel) => { + const createTimeField = (value: number, min: string, max: string, unitLabel: string): { wrap: HTMLElement; input: HTMLInputElement } => { const wrap = document.createElement("div"); wrap.style.cssText = "display: flex; align-items: center; gap: 6px;"; const input = document.createElement("input"); @@ -139,14 +158,14 @@ export class SpanPanelCardEditor extends HTMLElement { return { wrap, input }; }; - const daysValue = parseInt(this._config.history_days) || 0; - const hoursValue = parseInt(this._config.history_hours) || 0; - const minsValue = parseInt(this._config.history_minutes) || 0; + const daysValue = parseInt(String(this._config.history_days)) || 0; + const hoursValue = parseInt(String(this._config.history_hours)) || 0; + const minsValue = parseInt(String(this._config.history_minutes)) || 0; const days = createTimeField(daysValue, "0", "30", t("editor.days")); const hours = createTimeField(hoursValue, "0", "23", t("editor.hours")); const mins = createTimeField(minsValue, "0", "59", t("editor.minutes")); - const fireTimeChange = () => { + const fireTimeChange = (): void => { this._config = { ...this._config, history_days: parseInt(days.input.value) || 0, @@ -171,7 +190,7 @@ export class SpanPanelCardEditor extends HTMLElement { this._minsInput = mins.input; } - _buildMetricSelector(wrapper, fieldStyle, labelStyle, groupStyle) { + private _buildMetricSelector(wrapper: HTMLElement, fieldStyle: string, labelStyle: string, groupStyle: string): void { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); @@ -191,7 +210,7 @@ export class SpanPanelCardEditor extends HTMLElement { this._metricSelect = select; } - _buildSectionCheckboxes(wrapper, labelStyle, groupStyle) { + private _buildSectionCheckboxes(wrapper: HTMLElement, labelStyle: string, groupStyle: string): void { const group = document.createElement("div"); group.style.cssText = groupStyle; const label = document.createElement("label"); @@ -202,7 +221,7 @@ export class SpanPanelCardEditor extends HTMLElement { const checkboxStyle = "display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;"; const cbLabelStyle = "font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;"; - const sections = [ + const sections: { key: keyof CardConfig; label: string; subDeviceType: string | null }[] = [ { key: "show_panel", label: t("editor.panel_circuits"), subDeviceType: null }, { key: "show_battery", label: t("editor.battery_bess"), subDeviceType: "bess" }, { key: "show_evse", label: t("editor.ev_charger_evse"), subDeviceType: "evse" }, @@ -226,7 +245,7 @@ export class SpanPanelCardEditor extends HTMLElement { group.appendChild(row); this._checkboxes[sec.key] = cb; - let entityContainer = null; + let entityContainer: HTMLElement | null = null; if (sec.subDeviceType) { entityContainer = document.createElement("div"); entityContainer.style.cssText = "padding-left: 26px;"; @@ -245,9 +264,9 @@ export class SpanPanelCardEditor extends HTMLElement { wrapper.appendChild(group); } - _isChartEntity(entityId, info, subDeviceType) { - const name = (info.original_name || "").toLowerCase(); - const uid = info.unique_id || ""; + private _isChartEntity(_entityId: string, info: SubDeviceEntityInfo, subDeviceType: string): boolean { + const name = (info.original_name ?? "").toLowerCase(); + const uid = info.unique_id ?? ""; if (name === "power" || name === "battery power" || uid.endsWith("_power")) return true; if (subDeviceType === "bess") { if (name === "battery level" || name === "battery percentage" || uid.endsWith("_battery_level") || uid.endsWith("_battery_percentage")) return true; @@ -257,19 +276,19 @@ export class SpanPanelCardEditor extends HTMLElement { return false; } - _populateEntityCheckboxes(subDevices) { - const visibleEnts = this._config.visible_sub_entities || {}; + private _populateEntityCheckboxes(subDevices: Record): void { + const visibleEnts = this._config.visible_sub_entities ?? {}; const checkboxStyle = "display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;"; const cbLabelStyle = "font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;"; for (const [, sub] of Object.entries(subDevices)) { - const container = this._entityContainers[sub.type]; + const container = sub.type ? this._entityContainers[sub.type] : undefined; if (!container) continue; container.innerHTML = ""; if (!sub.entities) continue; for (const [entityId, info] of Object.entries(sub.entities)) { - if (info.domain === "sensor" && this._isChartEntity(entityId, info, sub.type)) continue; + if (info.domain === "sensor" && this._isChartEntity(entityId, info, sub.type ?? "")) continue; const row = document.createElement("div"); row.style.cssText = checkboxStyle; const cb = document.createElement("input"); @@ -277,8 +296,8 @@ export class SpanPanelCardEditor extends HTMLElement { cb.checked = visibleEnts[entityId] === true; cb.style.cssText = "width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);"; const lbl = document.createElement("span"); - let name = info.original_name || entityId; - const devName = sub.name || ""; + let name = info.original_name ?? entityId; + const devName = sub.name ?? ""; if (name.startsWith(devName + " ")) name = name.slice(devName.length + 1); lbl.textContent = name; lbl.style.cssText = cbLabelStyle; @@ -287,7 +306,7 @@ export class SpanPanelCardEditor extends HTMLElement { container.appendChild(row); cb.addEventListener("change", () => { - const updated = { ...(this._config.visible_sub_entities || {}) }; + const updated: Record = { ...(this._config.visible_sub_entities ?? {}) }; if (cb.checked) { updated[entityId] = true; } else { @@ -300,16 +319,16 @@ export class SpanPanelCardEditor extends HTMLElement { } } - async _discoverAvailableRoles(deviceId) { + private async _discoverAvailableRoles(deviceId: string): Promise { if (!this._hass || !deviceId) return; try { - const topo = await this._hass.callWS({ + const topo = await this._hass.callWS({ type: `${INTEGRATION_DOMAIN}/panel_topology`, device_id: deviceId, }); - const roles = new Set(); - for (const circuit of Object.values(topo.circuits || {})) { - for (const role of Object.keys(circuit.entities || {})) { + const roles = new Set(); + for (const circuit of Object.values(topo.circuits ?? {})) { + for (const role of Object.keys(circuit.entities ?? {})) { roles.add(role); } } @@ -324,12 +343,12 @@ export class SpanPanelCardEditor extends HTMLElement { } } - _populateMetricSelect() { + private _populateMetricSelect(): void { const select = this._metricSelect; if (!select) return; - const current = this._config.chart_metric || DEFAULT_CHART_METRIC; + const current = this._config.chart_metric ?? DEFAULT_CHART_METRIC; select.innerHTML = ""; - for (const [key, def] of Object.entries(CHART_METRICS)) { + for (const [key, def] of Object.entries(CHART_METRICS) as [string, ChartMetricDef][]) { if (this._availableRoles && !this._availableRoles.has(def.entityRole)) continue; const opt = document.createElement("option"); opt.value = key; @@ -339,20 +358,20 @@ export class SpanPanelCardEditor extends HTMLElement { } } - _updateControls() { - if (this._panelSelect) this._panelSelect.value = this._config.device_id || ""; - if (this._daysInput) this._daysInput.value = String(parseInt(this._config.history_days) || 0); - if (this._hoursInput) this._hoursInput.value = String(parseInt(this._config.history_hours) || 0); - if (this._minsInput) this._minsInput.value = String(parseInt(this._config.history_minutes) || 0); - if (this._metricSelect) this._metricSelect.value = this._config.chart_metric || DEFAULT_CHART_METRIC; + private _updateControls(): void { + if (this._panelSelect) this._panelSelect.value = this._config.device_id ?? ""; + if (this._daysInput) this._daysInput.value = String(parseInt(String(this._config.history_days)) || 0); + if (this._hoursInput) this._hoursInput.value = String(parseInt(String(this._config.history_hours)) || 0); + if (this._minsInput) this._minsInput.value = String(parseInt(String(this._config.history_minutes)) || 0); + if (this._metricSelect) this._metricSelect.value = this._config.chart_metric ?? DEFAULT_CHART_METRIC; if (this._checkboxes) { for (const [key, cb] of Object.entries(this._checkboxes)) { - cb.checked = this._config[key] !== false; + cb.checked = (this._config as Record)[key] !== false; } } } - _fireConfigChanged() { + private _fireConfigChanged(): void { this.dispatchEvent(new CustomEvent("config-changed", { detail: { config: this._config } })); } } diff --git a/src/helpers/chart.js b/src/helpers/chart.js deleted file mode 100644 index a27fe05..0000000 --- a/src/helpers/chart.js +++ /dev/null @@ -1,14 +0,0 @@ -import { CHART_METRICS, DEFAULT_CHART_METRIC } from "../constants.js"; - -export function getChartMetric(config) { - return CHART_METRICS[config.chart_metric] || CHART_METRICS[DEFAULT_CHART_METRIC]; -} - -export function getChartEntityRole(config) { - return getChartMetric(config).entityRole; -} - -export function getCircuitChartEntity(circuit, config) { - const role = getChartEntityRole(config); - return circuit.entities?.[role] || circuit.entities?.power || null; -} diff --git a/src/helpers/chart.ts b/src/helpers/chart.ts new file mode 100644 index 0000000..4ce8e42 --- /dev/null +++ b/src/helpers/chart.ts @@ -0,0 +1,16 @@ +import { CHART_METRICS, DEFAULT_CHART_METRIC } from "../constants.js"; +import type { CardConfig, ChartMetricDef, Circuit } from "../types.js"; + +export function getChartMetric(config: CardConfig): ChartMetricDef { + const key = config.chart_metric ?? DEFAULT_CHART_METRIC; + return CHART_METRICS[key] ?? CHART_METRICS[DEFAULT_CHART_METRIC]!; +} + +export function getChartEntityRole(config: CardConfig): string { + return getChartMetric(config).entityRole; +} + +export function getCircuitChartEntity(circuit: Circuit, config: CardConfig): string | null { + const role = getChartEntityRole(config); + return circuit.entities?.[role] ?? circuit.entities?.power ?? null; +} diff --git a/src/helpers/entity-finder.js b/src/helpers/entity-finder.js deleted file mode 100644 index 13e6f57..0000000 --- a/src/helpers/entity-finder.js +++ /dev/null @@ -1,38 +0,0 @@ -// ── Entity descriptor table ────────────────────────────────────────────────── -// -// Each entry describes how to locate a specific sensor entity within a -// sub-device's entity map by matching on the friendly name or unique_id suffix. - -const ENTITY_DESCRIPTORS = { - power: { names: ["power", "battery power"], suffixes: ["_power"] }, - soc: { names: ["battery level", "battery percentage"], suffixes: ["_battery_level", "_battery_percentage"] }, - soe: { names: ["state of energy"], suffixes: ["_soe_kwh"] }, - capacity: { names: ["nameplate capacity"], suffixes: ["_nameplate_capacity"] }, -}; - -function findSubDeviceEntity(subDevice, descriptor) { - if (!subDevice.entities) return null; - for (const [entityId, info] of Object.entries(subDevice.entities)) { - if (info.domain !== "sensor") continue; - const name = (info.original_name || "").toLowerCase(); - if (descriptor.names.some(n => name === n)) return entityId; - if (info.unique_id && descriptor.suffixes.some(s => info.unique_id.endsWith(s))) return entityId; - } - return null; -} - -export function findSubDevicePowerEntity(subDevice) { - return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.power); -} - -export function findBatteryLevelEntity(subDevice) { - return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.soc); -} - -export function findBatterySoeEntity(subDevice) { - return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.soe); -} - -export function findBatteryCapacityEntity(subDevice) { - return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.capacity); -} diff --git a/src/helpers/entity-finder.ts b/src/helpers/entity-finder.ts new file mode 100644 index 0000000..53b82d2 --- /dev/null +++ b/src/helpers/entity-finder.ts @@ -0,0 +1,35 @@ +import type { SubDevice, EntityDescriptor } from "../types.js"; + +const ENTITY_DESCRIPTORS: Record = { + power: { names: ["power", "battery power"], suffixes: ["_power"] }, + soc: { names: ["battery level", "battery percentage"], suffixes: ["_battery_level", "_battery_percentage"] }, + soe: { names: ["state of energy"], suffixes: ["_soe_kwh"] }, + capacity: { names: ["nameplate capacity"], suffixes: ["_nameplate_capacity"] }, +}; + +function findSubDeviceEntity(subDevice: SubDevice, descriptor: EntityDescriptor): string | null { + if (!subDevice.entities) return null; + for (const [entityId, info] of Object.entries(subDevice.entities)) { + if (info.domain !== "sensor") continue; + const name = (info.original_name ?? "").toLowerCase(); + if (descriptor.names.some(n => name === n)) return entityId; + if (info.unique_id && descriptor.suffixes.some(s => info.unique_id!.endsWith(s))) return entityId; + } + return null; +} + +export function findSubDevicePowerEntity(subDevice: SubDevice): string | null { + return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.power!); +} + +export function findBatteryLevelEntity(subDevice: SubDevice): string | null { + return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.soc!); +} + +export function findBatterySoeEntity(subDevice: SubDevice): string | null { + return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.soe!); +} + +export function findBatteryCapacityEntity(subDevice: SubDevice): string | null { + return findSubDeviceEntity(subDevice, ENTITY_DESCRIPTORS.capacity!); +} diff --git a/src/helpers/format.js b/src/helpers/format.js deleted file mode 100644 index 10ebdad..0000000 --- a/src/helpers/format.js +++ /dev/null @@ -1,20 +0,0 @@ -import { CHART_METRICS } from "../constants.js"; - -const powerMetric = CHART_METRICS.power; - -export function formatPower(watts) { - return powerMetric.format(watts); -} - -export function formatPowerUnit(watts) { - return powerMetric.unit(watts); -} - -export function formatPowerSigned(watts) { - const sign = watts < 0 ? "-" : ""; - return sign + powerMetric.format(watts); -} - -export function formatKw(watts) { - return (Math.abs(watts) / 1000).toFixed(1); -} diff --git a/src/helpers/format.ts b/src/helpers/format.ts new file mode 100644 index 0000000..a6b0ab0 --- /dev/null +++ b/src/helpers/format.ts @@ -0,0 +1,16 @@ +import { CHART_METRICS } from "../constants.js"; + +const powerMetric = CHART_METRICS.power!; + +export function formatPowerUnit(watts: number): string { + return powerMetric.unit(watts); +} + +export function formatPowerSigned(watts: number): string { + const sign = watts < 0 ? "-" : ""; + return sign + powerMetric.format(watts); +} + +export function formatKw(watts: number): string { + return (Math.abs(watts) / 1000).toFixed(1); +} diff --git a/src/helpers/history.js b/src/helpers/history.js deleted file mode 100644 index 57dafaa..0000000 --- a/src/helpers/history.js +++ /dev/null @@ -1,53 +0,0 @@ -import { DEFAULT_HISTORY_DAYS, DEFAULT_HISTORY_HOURS, DEFAULT_HISTORY_MINUTES, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; - -export function getHistoryDurationMs(config) { - const hasAny = config.history_days !== undefined || config.history_hours !== undefined || config.history_minutes !== undefined; - const d = hasAny ? parseInt(config.history_days) || 0 : DEFAULT_HISTORY_DAYS; - const h = hasAny ? parseInt(config.history_hours) || 0 : DEFAULT_HISTORY_HOURS; - const m = hasAny ? parseInt(config.history_minutes) || 0 : DEFAULT_HISTORY_MINUTES; - const total = ((d * 24 + h) * 60 + m) * 60 * 1000; - return Math.max(total, 60000); -} - -/** - * Get duration in ms for a horizon key. - * @param {string} horizonKey - e.g. "5m", "1h", "1d", "1M" - * @returns {number} Duration in milliseconds - */ -export function getHorizonDurationMs(horizonKey) { - const h = GRAPH_HORIZONS[horizonKey]; - return h ? h.ms : GRAPH_HORIZONS[DEFAULT_GRAPH_HORIZON].ms; -} - -export function getMaxHistoryPoints(durationMs) { - const seconds = durationMs / 1000; - if (seconds <= 600) return Math.ceil(seconds); - return Math.min(5000, Math.ceil(seconds / 5)); -} - -export function getMinGapMs(durationMs) { - return Math.max(500, Math.floor(durationMs / 5000)); -} - -// Record a single sample into a history map, pruning old entries. -export function recordSample(historyMap, key, value, now, cutoff, maxPoints) { - if (!historyMap.has(key)) historyMap.set(key, []); - const hist = historyMap.get(key); - hist.push({ time: now, value }); - while (hist.length > 0 && hist[0].time < cutoff) hist.shift(); - if (hist.length > maxPoints) hist.splice(0, hist.length - maxPoints); -} - -// Merge, deduplicate (by minGapMs), and trim a list of history points. -export function deduplicateAndTrim(points, maxPoints, minGapMs = 500) { - if (points.length === 0) return points; - points.sort((a, b) => a.time - b.time); - const deduped = [points[0]]; - for (let i = 1; i < points.length; i++) { - if (points[i].time - deduped[deduped.length - 1].time >= minGapMs) { - deduped.push(points[i]); - } - } - if (deduped.length > maxPoints) deduped.splice(0, deduped.length - maxPoints); - return deduped; -} diff --git a/src/helpers/history.ts b/src/helpers/history.ts new file mode 100644 index 0000000..6c7ad95 --- /dev/null +++ b/src/helpers/history.ts @@ -0,0 +1,65 @@ +import { + DEFAULT_HISTORY_DAYS, + DEFAULT_HISTORY_HOURS, + DEFAULT_HISTORY_MINUTES, + GRAPH_HORIZONS, + DEFAULT_GRAPH_HORIZON, + MIN_HISTORY_DURATION_MS, +} from "../constants.js"; +import type { CardConfig, HistoryPoint, HistoryMap } from "../types.js"; + +export function getHistoryDurationMs(config: CardConfig): number { + const hasAny = config.history_days !== undefined || config.history_hours !== undefined || config.history_minutes !== undefined; + const d = hasAny ? parseInt(String(config.history_days)) || 0 : DEFAULT_HISTORY_DAYS; + const h = hasAny ? parseInt(String(config.history_hours)) || 0 : DEFAULT_HISTORY_HOURS; + const m = hasAny ? parseInt(String(config.history_minutes)) || 0 : DEFAULT_HISTORY_MINUTES; + const total = ((d * 24 + h) * 60 + m) * 60 * 1000; + return Math.max(total, MIN_HISTORY_DURATION_MS); +} + +export function getHorizonDurationMs(horizonKey: string): number { + const h = GRAPH_HORIZONS[horizonKey]; + return h ? h.ms : GRAPH_HORIZONS[DEFAULT_GRAPH_HORIZON]!.ms; +} + +export function getMaxHistoryPoints(durationMs: number): number { + const seconds = durationMs / 1000; + if (seconds <= 600) return Math.ceil(seconds); + return Math.min(5000, Math.ceil(seconds / 5)); +} + +export function getMinGapMs(durationMs: number): number { + return Math.max(500, Math.floor(durationMs / 5000)); +} + +// Record a single sample into a history map, pruning old entries. +// Uses findIndex + splice instead of shift() loop for O(1) amortized pruning. +export function recordSample(historyMap: HistoryMap, key: string, value: number, now: number, cutoff: number, maxPoints: number): void { + if (!historyMap.has(key)) historyMap.set(key, []); + const hist = historyMap.get(key)!; + hist.push({ time: now, value }); + + // Prune entries older than cutoff using binary-style findIndex + splice + const firstValid = hist.findIndex(p => p.time >= cutoff); + if (firstValid > 0) { + hist.splice(0, firstValid); + } else if (firstValid === -1) { + hist.length = 0; + } + + if (hist.length > maxPoints) hist.splice(0, hist.length - maxPoints); +} + +// Merge, deduplicate (by minGapMs), and trim a list of history points. +export function deduplicateAndTrim(points: HistoryPoint[], maxPoints: number, minGapMs: number = 500): HistoryPoint[] { + if (points.length === 0) return points; + points.sort((a, b) => a.time - b.time); + const deduped: HistoryPoint[] = [points[0]!]; + for (let i = 1; i < points.length; i++) { + if (points[i]!.time - deduped[deduped.length - 1]!.time >= minGapMs) { + deduped.push(points[i]!); + } + } + if (deduped.length > maxPoints) deduped.splice(0, deduped.length - maxPoints); + return deduped; +} diff --git a/src/helpers/layout.js b/src/helpers/layout.ts similarity index 58% rename from src/helpers/layout.js rename to src/helpers/layout.ts index 31a8843..0004bf8 100644 --- a/src/helpers/layout.js +++ b/src/helpers/layout.ts @@ -1,12 +1,14 @@ -export function tabToRow(tab) { +import type { DualTabLayout } from "../types.js"; + +export function tabToRow(tab: number): number { return Math.ceil(tab / 2); } -export function tabToCol(tab) { +export function tabToCol(tab: number): number { return tab % 2 === 0 ? 1 : 0; } -export function classifyDualTab(tabs) { +export function classifyDualTab(tabs: number[]): DualTabLayout { if (tabs.length !== 2) return null; const [a, b] = [Math.min(...tabs), Math.max(...tabs)]; if (tabToRow(a) === tabToRow(b)) return "row-span"; diff --git a/src/helpers/sanitize.js b/src/helpers/sanitize.js deleted file mode 100644 index 74f6905..0000000 --- a/src/helpers/sanitize.js +++ /dev/null @@ -1,5 +0,0 @@ -const ESC_MAP = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; - -export function escapeHtml(str) { - return String(str).replace(/[&<>"']/g, c => ESC_MAP[c]); -} diff --git a/src/helpers/sanitize.ts b/src/helpers/sanitize.ts new file mode 100644 index 0000000..df96a73 --- /dev/null +++ b/src/helpers/sanitize.ts @@ -0,0 +1,5 @@ +const ESC_MAP: Record = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; + +export function escapeHtml(str: unknown): string { + return String(str).replace(/[&<>"']/g, c => ESC_MAP[c] ?? c); +} diff --git a/src/i18n.js b/src/i18n.ts similarity index 99% rename from src/i18n.js rename to src/i18n.ts index f18da0c..abcc470 100644 --- a/src/i18n.js +++ b/src/i18n.ts @@ -9,7 +9,7 @@ let _lang = "en"; -const translations = { +const translations: Record> = { en: { // Tab labels "tab.panel": "Panel", @@ -758,14 +758,14 @@ const translations = { * Set the active language. Call once per render cycle with hass.language. * Falls back to "en" for unsupported languages. */ -export function setLanguage(lang) { - _lang = translations[lang] ? lang : "en"; +export function setLanguage(lang: string | undefined): void { + _lang = lang && translations[lang] ? lang : "en"; } /** * Look up a translation key. Returns the English fallback when the key * is missing in the active language. */ -export function t(key) { - return translations[_lang]?.[key] ?? translations.en[key] ?? key; +export function t(key: string): string { + return translations[_lang]?.[key] ?? translations.en?.[key] ?? key; } diff --git a/src/index.js b/src/index.ts similarity index 80% rename from src/index.js rename to src/index.ts index b3f598b..1af355e 100644 --- a/src/index.js +++ b/src/index.ts @@ -9,7 +9,20 @@ if (!customElements.get("span-panel-card-editor")) { customElements.define("span-panel-card-editor", SpanPanelCardEditor); } -window.customCards = window.customCards || []; +interface CustomCardDef { + type: string; + name: string; + description: string; + preview: boolean; +} + +declare global { + interface Window { + customCards?: CustomCardDef[]; + } +} + +window.customCards = window.customCards ?? []; window.customCards.push({ type: "span-panel-card", name: "SPAN Panel", diff --git a/src/panel/index.js b/src/panel/index.ts similarity index 100% rename from src/panel/index.js rename to src/panel/index.ts diff --git a/src/panel/span-panel.js b/src/panel/span-panel.ts similarity index 66% rename from src/panel/span-panel.js rename to src/panel/span-panel.ts index 069e600..e398661 100644 --- a/src/panel/span-panel.js +++ b/src/panel/span-panel.ts @@ -1,9 +1,16 @@ import { INTEGRATION_DOMAIN } from "../constants.js"; +import { escapeHtml } from "../helpers/sanitize.js"; import { setLanguage, t } from "../i18n.js"; import "../core/side-panel.js"; import { DashboardTab } from "./tab-dashboard.js"; import { MonitoringTab } from "./tab-monitoring.js"; import { SettingsTab } from "./tab-settings.js"; +import type { HomeAssistant, PanelDevice, CardConfig } from "../types.js"; + +interface HaMenuButton extends HTMLElement { + hass: HomeAssistant; + narrow: boolean; +} const PANEL_STYLES = ` :host { @@ -76,12 +83,27 @@ const PANEL_STYLES = ` } `; +type TabName = "dashboard" | "monitoring" | "settings"; + export class SpanPanelElement extends HTMLElement { + private _hass: HomeAssistant | null; + // _config is set by HA but dashboard builds its own config + private _panels: PanelDevice[]; + private _selectedPanelId: string | null; + private _activeTab: TabName; + private _discovered: boolean; + private _narrow: boolean; + private _dashboardTab: DashboardTab; + private _monitoringTab: MonitoringTab; + private _settingsTab: SettingsTab; + private _chartMetric: string | undefined; + private _onVisibilityChange: (() => void) | null; + private _deviceRegistryUnsub: Promise<() => void> | null; + constructor() { super(); this.attachShadow({ mode: "open" }); this._hass = null; - this._config = {}; this._panels = []; this._selectedPanelId = null; this._activeTab = "dashboard"; @@ -90,15 +112,17 @@ export class SpanPanelElement extends HTMLElement { this._dashboardTab = new DashboardTab(); this._monitoringTab = new MonitoringTab(); this._settingsTab = new SettingsTab(); + this._onVisibilityChange = null; + this._deviceRegistryUnsub = null; } - connectedCallback() { + connectedCallback(): void { // When HA navigates back to this panel, re-render if we already have data if (this._discovered && this._hass) { this._render(); } - this._onVisibilityChange = () => { + this._onVisibilityChange = (): void => { if (document.visibilityState === "visible" && this._discovered && this._hass) { this._renderTab(); } @@ -108,7 +132,7 @@ export class SpanPanelElement extends HTMLElement { this._subscribeDeviceRegistry(); } - disconnectedCallback() { + disconnectedCallback(): void { this._dashboardTab.stop(); this._monitoringTab.stop(); this._settingsTab.stop(); @@ -119,25 +143,25 @@ export class SpanPanelElement extends HTMLElement { this._unsubscribeDeviceRegistry(); } - _subscribeDeviceRegistry() { + private _subscribeDeviceRegistry(): void { if (this._deviceRegistryUnsub || !this._hass?.connection) return; this._deviceRegistryUnsub = this._hass.connection.subscribeEvents(() => this._refreshPanels(), "device_registry_updated"); } - _unsubscribeDeviceRegistry() { + private _unsubscribeDeviceRegistry(): void { if (this._deviceRegistryUnsub) { this._deviceRegistryUnsub.then(unsub => unsub()); this._deviceRegistryUnsub = null; } } - async _refreshPanels() { + private async _refreshPanels(): Promise { if (!this._hass || !this._discovered) return; - const devices = await this._hass.callWS({ + const devices = await this._hass.callWS({ type: "config/device_registry/list", }); - const panels = devices.filter(d => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN) && !d.via_device_id); + const panels = devices.filter((d: PanelDevice) => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN) && !d.via_device_id); const currentIds = new Set(this._panels.map(p => p.id)); const newIds = new Set(panels.map(p => p.id)); @@ -145,18 +169,18 @@ export class SpanPanelElement extends HTMLElement { this._panels = panels; if (!this._panels.some(p => p.id === this._selectedPanelId) && this._panels.length > 0) { - this._selectedPanelId = this._panels[0].id; + this._selectedPanelId = this._panels[0]!.id; localStorage.setItem("span_panel_selected", this._selectedPanelId); } this._render(); } - set hass(val) { + set hass(val: HomeAssistant) { const firstHass = !this._hass && val; this._hass = val; - this._dashboardTab._hass = val; + this._dashboardTab.hass = val; // Update ha-menu-button if already rendered - const menuBtn = this.shadowRoot.querySelector("ha-menu-button"); + const menuBtn = this.shadowRoot!.querySelector("ha-menu-button"); if (menuBtn) menuBtn.hass = val; if (!this._discovered) { this._discoverPanels(); @@ -166,30 +190,30 @@ export class SpanPanelElement extends HTMLElement { } } - set narrow(val) { + set narrow(val: boolean) { this._narrow = val; - const menuBtn = this.shadowRoot.querySelector("ha-menu-button"); + const menuBtn = this.shadowRoot!.querySelector("ha-menu-button"); if (menuBtn) menuBtn.narrow = val; } - setConfig(config) { - this._config = config || {}; + setConfig(_config: CardConfig): void { + // Config is set by HA but the dashboard tab builds its own config } - async _discoverPanels() { + private async _discoverPanels(): Promise { if (!this._hass) return; this._discovered = true; - const devices = await this._hass.callWS({ + const devices = await this._hass.callWS({ type: "config/device_registry/list", }); - this._panels = devices.filter(d => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN) && !d.via_device_id); + this._panels = devices.filter((d: PanelDevice) => d.identifiers?.some(id => id[0] === INTEGRATION_DOMAIN) && !d.via_device_id); const stored = localStorage.getItem("span_panel_selected"); if (stored && this._panels.some(p => p.id === stored)) { this._selectedPanelId = stored; } else if (this._panels.length > 0) { - this._selectedPanelId = this._panels[0].id; + this._selectedPanelId = this._panels[0]!.id; } this._chartMetric = localStorage.getItem("span_panel_metric") || "power"; @@ -197,9 +221,9 @@ export class SpanPanelElement extends HTMLElement { this._render(); } - _render() { + private _render(): void { setLanguage(this._hass?.language); - this.shadowRoot.innerHTML = ` + this.shadowRoot!.innerHTML = `
@@ -212,7 +236,7 @@ export class SpanPanelElement extends HTMLElement { .map( p => ` ` ) @@ -237,13 +261,13 @@ export class SpanPanelElement extends HTMLElement { `; // Wire up ha-menu-button - const menuBtn = this.shadowRoot.querySelector("ha-menu-button"); + const menuBtn = this.shadowRoot!.querySelector("ha-menu-button"); if (menuBtn) { - menuBtn.hass = this._hass; + menuBtn.hass = this._hass!; menuBtn.narrow = this._narrow; } - const select = this.shadowRoot.getElementById("panel-select"); + const select = this.shadowRoot!.getElementById("panel-select") as HTMLSelectElement | null; if (select) { select.addEventListener("change", () => { this._selectedPanelId = select.value; @@ -252,11 +276,11 @@ export class SpanPanelElement extends HTMLElement { }); } - for (const tab of this.shadowRoot.querySelectorAll(".panel-tab")) { + for (const tab of this.shadowRoot!.querySelectorAll(".panel-tab")) { tab.addEventListener("click", () => { - this._activeTab = tab.dataset.tab; - for (const t of this.shadowRoot.querySelectorAll(".panel-tab")) { - t.classList.toggle("active", t.dataset.tab === this._activeTab); + this._activeTab = tab.dataset.tab as TabName; + for (const tabEl of this.shadowRoot!.querySelectorAll(".panel-tab")) { + tabEl.classList.toggle("active", tabEl.dataset.tab === this._activeTab); } this._renderTab(); }); @@ -267,7 +291,7 @@ export class SpanPanelElement extends HTMLElement { // Sync: if graph settings change (from side panel or settings tab), // re-render settings tab if it's visible - this.shadowRoot.addEventListener("graph-settings-changed", () => { + this.shadowRoot!.addEventListener("graph-settings-changed", () => { if (this._activeTab === "settings") { this._renderTab(); } @@ -276,9 +300,10 @@ export class SpanPanelElement extends HTMLElement { this._renderTab(); } - _bindUnitToggle() { - this.shadowRoot.addEventListener("click", e => { - const btn = e.target.closest(".unit-btn"); + private _bindUnitToggle(): void { + this.shadowRoot!.addEventListener("click", (e: Event) => { + const target = e.target as HTMLElement; + const btn = target.closest(".unit-btn"); if (!btn) return; const metric = btn.dataset.unit; if (!metric || metric === this._chartMetric) return; @@ -290,19 +315,19 @@ export class SpanPanelElement extends HTMLElement { }); } - _bindTabNavigation() { - this.shadowRoot.addEventListener("navigate-tab", e => { - const tab = e.detail; + private _bindTabNavigation(): void { + this.shadowRoot!.addEventListener("navigate-tab", (e: Event) => { + const tab = (e as CustomEvent).detail; if (!tab) return; - this._activeTab = tab; - for (const t of this.shadowRoot.querySelectorAll(".panel-tab")) { - t.classList.toggle("active", t.dataset.tab === tab); + this._activeTab = tab as TabName; + for (const tabEl of this.shadowRoot!.querySelectorAll(".panel-tab")) { + tabEl.classList.toggle("active", tabEl.dataset.tab === tab); } this._renderTab(); }); } - _buildDashboardConfig() { + private _buildDashboardConfig(): CardConfig { return { chart_metric: this._chartMetric, history_minutes: 5, @@ -312,10 +337,10 @@ export class SpanPanelElement extends HTMLElement { }; } - async _renderTab() { + private async _renderTab(): Promise { this._dashboardTab.stop(); - const container = this.shadowRoot.getElementById("tab-content"); + const container = this.shadowRoot!.getElementById("tab-content"); if (!container) return; switch (this._activeTab) { @@ -323,22 +348,22 @@ export class SpanPanelElement extends HTMLElement { container.innerHTML = ""; const config = this._buildDashboardConfig(); const dashDevice = this._panels.find(p => p.id === this._selectedPanelId); - const dashEntryId = dashDevice?.config_entries?.[0] || null; - await this._dashboardTab.render(container, this._hass, this._selectedPanelId, config, dashEntryId); + const dashEntryId = dashDevice?.config_entries?.[0] ?? null; + await this._dashboardTab.render(container, this._hass!, this._selectedPanelId ?? "", config, dashEntryId); break; } case "monitoring": { container.innerHTML = ""; const monDevice = this._panels.find(p => p.id === this._selectedPanelId); - const monEntryId = monDevice?.config_entries?.[0] || null; - await this._monitoringTab.render(container, this._hass, monEntryId); + const monEntryId = monDevice?.config_entries?.[0] ?? null; + await this._monitoringTab.render(container, this._hass!, monEntryId ?? undefined); break; } case "settings": { container.innerHTML = ""; const selectedDevice = this._panels.find(p => p.id === this._selectedPanelId); - const configEntryId = selectedDevice?.config_entries?.[0] || null; - await this._settingsTab.render(container, this._hass, configEntryId, this._selectedPanelId); + const configEntryId = selectedDevice?.config_entries?.[0] ?? null; + await this._settingsTab.render(container, this._hass!, configEntryId ?? undefined, this._selectedPanelId ?? undefined); break; } } diff --git a/src/panel/tab-dashboard.js b/src/panel/tab-dashboard.js deleted file mode 100644 index f83aa03..0000000 --- a/src/panel/tab-dashboard.js +++ /dev/null @@ -1,452 +0,0 @@ -import { discoverTopology } from "../card/card-discovery.js"; -import { buildHeaderHTML } from "../core/header-renderer.js"; -import { buildGridHTML } from "../core/grid-renderer.js"; -import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; -import { updateCircuitDOM, updateSubDeviceDOM } from "../core/dom-updater.js"; -import { loadHistory, collectSubDeviceEntityIds } from "../core/history-loader.js"; -import { MonitoringStatusCache, buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; -import { GraphSettingsCache } from "../core/graph-settings.js"; -import { CARD_STYLES } from "../card/card-styles.js"; -import { getHistoryDurationMs, recordSample, getMaxHistoryPoints, getMinGapMs, getHorizonDurationMs } from "../helpers/history.js"; -import { getCircuitChartEntity } from "../helpers/chart.js"; -import { LIVE_SAMPLE_INTERVAL_MS, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; -import "../core/side-panel.js"; - -export class DashboardTab { - constructor() { - this._topology = null; - this._panelSize = 0; - this._powerHistory = new Map(); - this._monitoringCache = new MonitoringStatusCache(); - this._graphSettingsCache = new GraphSettingsCache(); - this._updateInterval = null; - this._recorderRefreshInterval = null; - this._horizonMap = new Map(); - this._subDeviceHorizonMap = new Map(); - this._hass = null; - this._config = null; - this._resizeObserver = null; - this._lastContainerWidth = 0; - this._resizeDebounce = null; - this._container = null; - } - - async render(container, hass, deviceId, config, configEntryId) { - this.stop(); - this._hass = hass; - this._powerHistory.clear(); - this._config = config; - this._container = container; - this._configEntryId = configEntryId || null; - - try { - const result = await discoverTopology(hass, deviceId); - this._topology = result.topology; - this._panelSize = result.panelSize; - } catch (err) { - container.innerHTML = `

${err.message}

`; - return; - } - - await this._monitoringCache.fetch(hass, this._configEntryId); - await this._graphSettingsCache.fetch(hass, this._configEntryId); - - const topo = this._topology; - - // Build per-circuit horizon map - this._horizonMap = new Map(); - const graphSettings = this._graphSettingsCache.settings; - if (topo?.circuits) { - for (const uuid of Object.keys(topo.circuits)) { - const override = graphSettings?.circuits?.[uuid]; - const horizon = override?.has_override ? override.horizon : graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - this._horizonMap.set(uuid, horizon); - } - } - // Build sub-device horizon map - if (topo?.sub_devices) { - for (const devId of Object.keys(topo.sub_devices)) { - const override = graphSettings?.sub_devices?.[devId]; - const horizon = override?.has_override ? override.horizon : graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - this._subDeviceHorizonMap.set(devId, horizon); - } - } - const totalRows = Math.ceil(this._panelSize / 2); - const durationMs = getHistoryDurationMs(config); - const monitoringStatus = this._monitoringCache.status; - - const headerHTML = buildHeaderHTML(topo, config); - const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); - const gridHTML = buildGridHTML(topo, totalRows, durationMs, hass, config, monitoringStatus); - const subDevHTML = buildSubDevicesHTML(topo, hass, config, durationMs); - - container.innerHTML = ` - - ${headerHTML} - ${monitoringSummaryHTML} - ${subDevHTML ? `
${subDevHTML}
` : ""} - ${ - config.show_panel !== false - ? ` -
- ${gridHTML} -
- ` - : "" - } - - `; - - this._bindGearClicks(container, topo); - this._bindToggleClicks(container, topo); - this._onSidePanelClosed = () => { - this._monitoringCache.invalidate(); - this._graphSettingsCache.invalidate(); - }; - this._onGraphSettingsChanged = async () => { - this._graphSettingsCache.invalidate(); - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - - // Rebuild horizon map - const newSettings = this._graphSettingsCache.settings; - if (topo?.circuits) { - for (const uuid of Object.keys(topo.circuits)) { - const override = newSettings?.circuits?.[uuid]; - const horizon = override?.has_override ? override.horizon : newSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - this._horizonMap.set(uuid, horizon); - } - } - - // Rebuild sub-device horizon map - if (topo?.sub_devices) { - for (const devId of Object.keys(topo.sub_devices)) { - const override = newSettings?.sub_devices?.[devId]; - const horizon = override?.has_override ? override.horizon : newSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - this._subDeviceHorizonMap.set(devId, horizon); - } - } - - // Reload all history with new horizons - this._powerHistory.clear(); - try { - await loadHistory(this._hass, topo, this._config, this._powerHistory, this._horizonMap, this._subDeviceHorizonMap); - } catch { - // Will populate on next refresh - } - updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory, this._subDeviceHorizonMap); - }; - container.addEventListener("side-panel-closed", this._onSidePanelClosed); - container.addEventListener("graph-settings-changed", this._onGraphSettingsChanged); - - try { - await loadHistory(hass, topo, config, this._powerHistory, this._horizonMap, this._subDeviceHorizonMap); - } catch { - // Charts will populate live - } - - // Initial DOM update with history data - updateCircuitDOM(container, hass, topo, config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(container, hass, topo, config, this._powerHistory, this._subDeviceHorizonMap); - - const slideEl = container.querySelector(".slide-confirm"); - if (slideEl) { - this._bindSlideConfirm(slideEl, container); - container.classList.add("switches-disabled"); - } - - this._setupResizeObserver(container, topo, config); - - // Start live update loop - this._updateInterval = setInterval(() => { - this._recordSamples(); - updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(container, this._hass, topo, this._config, this._powerHistory, this._subDeviceHorizonMap); - }, LIVE_SAMPLE_INTERVAL_MS); - - // Periodic recorder refresh for non-realtime horizons - this._recorderRefreshInterval = setInterval(async () => { - if (!this._topology || !this._hass) return; - const nonRealtimeMap = new Map(); - for (const [uuid, horizon] of this._horizonMap) { - if (!GRAPH_HORIZONS[horizon]?.useRealtime) { - nonRealtimeMap.set(uuid, horizon); - } - } - if (nonRealtimeMap.size === 0) return; - // Load into a temporary map so charts keep showing stale data - // until fresh data is ready (avoids blank-chart flash). - const freshHistory = new Map(); - try { - await loadHistory(this._hass, this._topology, this._config, freshHistory, nonRealtimeMap, this._subDeviceHorizonMap); - // Atomically replace only the non-realtime entries - for (const uuid of nonRealtimeMap.keys()) { - const data = freshHistory.get(uuid); - if (data) { - this._powerHistory.set(uuid, data); - } else { - this._powerHistory.delete(uuid); - } - } - updateCircuitDOM(container, this._hass, topo, this._config, this._powerHistory, this._horizonMap); - } catch { - // Recorder data will refresh on next interval - } - }, 30000); - } - - _recordSamples() { - if (!this._topology || !this._hass) return; - const now = Date.now(); - - for (const [uuid, circuit] of Object.entries(this._topology.circuits)) { - const horizon = this._horizonMap?.get(uuid) || DEFAULT_GRAPH_HORIZON; - if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; - - const eid = getCircuitChartEntity(circuit, this._config); - if (!eid) continue; - const state = this._hass.states[eid]; - if (!state) continue; - const val = parseFloat(state.state); - if (isNaN(val)) continue; - - const durationMs = getHorizonDurationMs(horizon); - const maxPoints = getMaxHistoryPoints(durationMs); - const minGap = getMinGapMs(durationMs); - const cutoff = now - durationMs; - - const hist = this._powerHistory.get(uuid) || []; - if (hist.length > 0 && now - hist[hist.length - 1].time < minGap) continue; - - recordSample(this._powerHistory, uuid, val, now, cutoff, maxPoints); - } - - // Sub-device samples with per-device horizons - for (const { entityId, key, devId } of collectSubDeviceEntityIds(this._topology)) { - const horizon = this._subDeviceHorizonMap?.get(devId) || DEFAULT_GRAPH_HORIZON; - if (!GRAPH_HORIZONS[horizon]?.useRealtime) continue; - - const state = this._hass.states[entityId]; - if (!state) continue; - const val = parseFloat(state.state); - if (isNaN(val)) continue; - - const durationMs = getHorizonDurationMs(horizon); - const maxPoints = getMaxHistoryPoints(durationMs); - const minGap = getMinGapMs(durationMs); - const cutoff = now - durationMs; - - const hist = this._powerHistory.get(key) || []; - if (hist.length > 0 && now - hist[hist.length - 1].time < minGap) continue; - - recordSample(this._powerHistory, key, val, now, cutoff, maxPoints); - } - } - - _bindSlideConfirm(slideEl, parent) { - const knob = slideEl.querySelector(".slide-confirm-knob"); - const textEl = slideEl.querySelector(".slide-confirm-text"); - if (!knob) return; - const THRESHOLD = 0.9; - let dragging = false; - let startX = 0; - let maxX = 0; - - const begin = clientX => { - if (slideEl.classList.contains("confirmed")) return; - dragging = true; - startX = clientX - knob.offsetLeft; - maxX = slideEl.offsetWidth - knob.offsetWidth - 4; - knob.classList.remove("snapping"); - }; - const move = clientX => { - if (!dragging) return; - const x = Math.max(2, Math.min(clientX - startX, maxX)); - knob.style.left = x + "px"; - }; - const end = () => { - if (!dragging) return; - dragging = false; - const pos = (knob.offsetLeft - 2) / maxX; - if (pos >= THRESHOLD) { - knob.style.left = maxX + "px"; - slideEl.classList.add("confirmed"); - knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock-open"); - textEl.textContent = slideEl.dataset.textOn; - if (parent) parent.classList.remove("switches-disabled"); - } else { - knob.classList.add("snapping"); - knob.style.left = "2px"; - } - }; - - knob.addEventListener("mousedown", e => { - e.preventDefault(); - begin(e.clientX); - }); - slideEl.addEventListener("mousemove", e => move(e.clientX)); - slideEl.addEventListener("mouseup", end); - slideEl.addEventListener("mouseleave", end); - knob.addEventListener( - "touchstart", - e => { - e.preventDefault(); - begin(e.touches[0].clientX); - }, - { passive: false } - ); - slideEl.addEventListener("touchmove", e => move(e.touches[0].clientX), { passive: true }); - slideEl.addEventListener("touchend", end); - slideEl.addEventListener("touchcancel", end); - - slideEl.addEventListener("click", () => { - if (!slideEl.classList.contains("confirmed")) return; - slideEl.classList.remove("confirmed"); - knob.classList.add("snapping"); - knob.style.left = "2px"; - knob.querySelector("ha-icon").setAttribute("icon", "mdi:lock"); - textEl.textContent = slideEl.dataset.textOff; - if (parent) parent.classList.add("switches-disabled"); - }); - } - - _bindToggleClicks(container, topology) { - container.addEventListener("click", e => { - const pill = e.target.closest(".toggle-pill"); - if (!pill) return; - const cb = container.querySelector(".slide-confirm"); - if (!cb || !cb.classList.contains("confirmed")) return; - e.stopPropagation(); - e.preventDefault(); - const slot = pill.closest("[data-uuid]"); - if (!slot || !topology || !this._hass) return; - const uuid = slot.dataset.uuid; - const circuit = topology.circuits[uuid]; - if (!circuit) return; - const switchEntity = circuit.entities?.switch; - if (!switchEntity) return; - const switchState = this._hass.states[switchEntity]; - if (!switchState) return; - const service = switchState.state === "on" ? "turn_off" : "turn_on"; - this._hass.callService("switch", service, {}, { entity_id: switchEntity }); - }); - } - - _bindGearClicks(container, topology) { - container.addEventListener("click", async e => { - const gearBtn = e.target.closest(".gear-icon"); - if (!gearBtn) return; - - const sidePanel = container.querySelector("span-side-panel"); - if (!sidePanel || !this._hass) return; - sidePanel.hass = this._hass; - - if (gearBtn.classList.contains("panel-gear")) { - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - sidePanel.open({ - panelMode: true, - topology, - graphSettings: this._graphSettingsCache.settings, - }); - return; - } - - const uuid = gearBtn.dataset.uuid; - if (uuid && topology) { - const circuit = topology.circuits[uuid]; - if (circuit) { - // Always fetch fresh monitoring and graph settings data before opening side panel - await this._monitoringCache.fetch(this._hass); - const monitoringInfo = this._monitoringCache?.status?.circuits?.[circuit.entities?.power] || null; - - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - const graphSettings = this._graphSettingsCache.settings; - const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - const graphHorizonInfo = graphSettings?.circuits?.[uuid] - ? { ...graphSettings.circuits[uuid], globalHorizon } - : { horizon: globalHorizon, has_override: false, globalHorizon }; - - sidePanel.open({ - ...circuit, - uuid, - monitoringInfo, - graphHorizonInfo, - }); - return; - } - } - - const subDevId = gearBtn.dataset.subdevId; - if (subDevId && topology?.sub_devices?.[subDevId]) { - const sub = topology.sub_devices[subDevId]; - - await this._graphSettingsCache.fetch(this._hass, this._configEntryId); - const graphSettings = this._graphSettingsCache.settings; - const globalHorizon = graphSettings?.global_horizon || DEFAULT_GRAPH_HORIZON; - const graphHorizonInfo = graphSettings?.sub_devices?.[subDevId] - ? { ...graphSettings.sub_devices[subDevId], globalHorizon } - : { horizon: globalHorizon, has_override: false, globalHorizon }; - - sidePanel.open({ - subDeviceMode: true, - subDeviceId: subDevId, - name: sub.name || subDevId, - deviceType: sub.type || "", - graphHorizonInfo, - }); - return; - } - }); - } - - _setupResizeObserver(container, topo, config) { - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - } - this._lastContainerWidth = container.clientWidth; - this._resizeObserver = new ResizeObserver(entries => { - const entry = entries[0]; - if (!entry) return; - const newWidth = entry.contentRect.width; - if (Math.abs(newWidth - this._lastContainerWidth) < 5) return; - this._lastContainerWidth = newWidth; - if (this._resizeDebounce) clearTimeout(this._resizeDebounce); - this._resizeDebounce = setTimeout(() => { - for (const cc of container.querySelectorAll(".chart-container")) { - const chart = cc.querySelector("ha-chart-base"); - if (chart) chart.remove(); - } - updateCircuitDOM(container, this._hass, topo, config, this._powerHistory, this._horizonMap); - updateSubDeviceDOM(container, this._hass, topo, config, this._powerHistory, this._subDeviceHorizonMap); - }, 150); - }); - this._resizeObserver.observe(container); - } - - stop() { - if (this._updateInterval) { - clearInterval(this._updateInterval); - this._updateInterval = null; - } - if (this._recorderRefreshInterval) { - clearInterval(this._recorderRefreshInterval); - this._recorderRefreshInterval = null; - } - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - this._resizeObserver = null; - } - if (this._resizeDebounce) { - clearTimeout(this._resizeDebounce); - this._resizeDebounce = null; - } - if (this._container && this._onSidePanelClosed) { - this._container.removeEventListener("side-panel-closed", this._onSidePanelClosed); - this._onSidePanelClosed = null; - } - if (this._container && this._onGraphSettingsChanged) { - this._container.removeEventListener("graph-settings-changed", this._onGraphSettingsChanged); - this._onGraphSettingsChanged = null; - } - } -} diff --git a/src/panel/tab-dashboard.ts b/src/panel/tab-dashboard.ts new file mode 100644 index 0000000..9536bf9 --- /dev/null +++ b/src/panel/tab-dashboard.ts @@ -0,0 +1,110 @@ +import { discoverTopology } from "../card/card-discovery.js"; +import { buildHeaderHTML } from "../core/header-renderer.js"; +import { buildGridHTML } from "../core/grid-renderer.js"; +import { buildSubDevicesHTML } from "../core/sub-device-renderer.js"; +import { buildMonitoringSummaryHTML } from "../core/monitoring-status.js"; +import { DashboardController } from "../core/dashboard-controller.js"; +import { CARD_STYLES } from "../card/card-styles.js"; +import "../core/side-panel.js"; +import type { HomeAssistant, CardConfig } from "../types.js"; + +export class DashboardTab { + private readonly _ctrl = new DashboardController(); + private _container: HTMLElement | null = null; + private _onSidePanelClosed: (() => void) | null = null; + private _onGraphSettingsChanged: (() => void) | null = null; + + get hass(): HomeAssistant | null { + return this._ctrl.hass; + } + + set hass(val: HomeAssistant | null) { + this._ctrl.hass = val; + } + + async render(container: HTMLElement, hass: HomeAssistant, deviceId: string, config: CardConfig, configEntryId?: string | null): Promise { + this.stop(); + this._container = container; + this._ctrl.hass = hass; + + let topology, panelSize; + try { + const result = await discoverTopology(hass, deviceId); + topology = result.topology; + panelSize = result.panelSize; + } catch (err) { + container.innerHTML = `

${(err as Error).message}

`; + return; + } + + this._ctrl.init(topology, config, hass, configEntryId ?? null); + await this._ctrl.monitoringCache.fetch(hass, configEntryId ?? null); + await this._ctrl.fetchAndBuildHorizonMaps(); + + const totalRows = Math.ceil(panelSize / 2); + const monitoringStatus = this._ctrl.monitoringCache.status; + + const headerHTML = buildHeaderHTML(topology!, config); + const monitoringSummaryHTML = buildMonitoringSummaryHTML(monitoringStatus); + const gridHTML = buildGridHTML(topology!, totalRows, hass, config, monitoringStatus); + const subDevHTML = buildSubDevicesHTML(topology!, hass, config); + + container.innerHTML = ` + + ${headerHTML} + ${monitoringSummaryHTML} + ${subDevHTML ? `
${subDevHTML}
` : ""} + ${ + config.show_panel !== false + ? ` +
+ ${gridHTML} +
+ ` + : "" + } + + `; + + container.addEventListener("click", (ev: Event) => this._ctrl.onGearClick(ev, container)); + container.addEventListener("click", (ev: Event) => this._ctrl.onToggleClick(ev, container)); + + this._onSidePanelClosed = () => { + this._ctrl.monitoringCache.invalidate(); + this._ctrl.graphSettingsCache.invalidate(); + }; + container.addEventListener("side-panel-closed", this._onSidePanelClosed); + + this._onGraphSettingsChanged = () => this._ctrl.onGraphSettingsChanged(container); + container.addEventListener("graph-settings-changed", this._onGraphSettingsChanged); + + try { + await this._ctrl.loadHistory(); + } catch { + // Charts will populate live + } + + this._ctrl.updateDOM(container); + + const slideEl = container.querySelector(".slide-confirm"); + if (slideEl) { + this._ctrl.bindSlideConfirm(slideEl, container); + container.classList.add("switches-disabled"); + } + + this._ctrl.setupResizeObserver(container, container); + this._ctrl.startIntervals(container); + } + + stop(): void { + this._ctrl.stopIntervals(); + if (this._container && this._onSidePanelClosed) { + this._container.removeEventListener("side-panel-closed", this._onSidePanelClosed); + this._onSidePanelClosed = null; + } + if (this._container && this._onGraphSettingsChanged) { + this._container.removeEventListener("graph-settings-changed", this._onGraphSettingsChanged); + this._onGraphSettingsChanged = null; + } + } +} diff --git a/src/panel/tab-monitoring.js b/src/panel/tab-monitoring.ts similarity index 78% rename from src/panel/tab-monitoring.js rename to src/panel/tab-monitoring.ts index b97b3e8..e58ef5c 100644 --- a/src/panel/tab-monitoring.js +++ b/src/panel/tab-monitoring.ts @@ -1,6 +1,7 @@ -import { INTEGRATION_DOMAIN } from "../constants.js"; +import { INTEGRATION_DOMAIN, INPUT_DEBOUNCE_MS, THRESHOLD_DEBOUNCE_MS } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; import { t } from "../i18n.js"; +import type { HomeAssistant, MonitoringPointInfo, MonitoringStatusResponse, CallServiceResponse } from "../types.js"; const FIELD_STYLE = ` display:flex;align-items:center;gap:8px;margin-bottom:8px; @@ -32,7 +33,7 @@ const CELL_INPUT_STYLE = ` text-align:center; `; -function thresholdCell(entityId, field, value, unit, type) { +function thresholdCell(entityId: string, field: string, value: number | undefined, unit: string, type: string): string { return ` | null; + private _configEntryId: string | null; + private _notifyCloseHandler: ((e: MouseEvent) => void) | null; + constructor() { this._debounceTimer = null; this._configEntryId = null; + this._notifyCloseHandler = null; } - stop() { + stop(): void { if (this._notifyCloseHandler) { - document.removeEventListener("click", this._notifyCloseHandler); + document.removeEventListener("click", this._notifyCloseHandler as EventListener); this._notifyCloseHandler = null; } if (this._debounceTimer) { @@ -57,43 +63,44 @@ export class MonitoringTab { } } - async render(container, hass, configEntryId) { + async render(container: HTMLElement, hass: HomeAssistant, configEntryId?: string): Promise { if (configEntryId !== undefined) this._configEntryId = configEntryId; if (this._notifyCloseHandler) { - document.removeEventListener("click", this._notifyCloseHandler); + document.removeEventListener("click", this._notifyCloseHandler as EventListener); this._notifyCloseHandler = null; } - let status; + let status: MonitoringStatusResponse | null; try { - const serviceData = {}; + const serviceData: Record = {}; if (this._configEntryId) serviceData.config_entry_id = this._configEntryId; - const resp = await hass.callWS({ + const resp = await hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, service: "get_monitoring_status", service_data: serviceData, return_response: true, }); - status = resp?.response || null; + status = (resp?.response as MonitoringStatusResponse) || null; } catch { status = null; } - const globalSettings = status?.global_settings || {}; + const globalSettings = status?.global_settings ?? {}; const isEnabled = status?.enabled === true; - const circuits = status?.circuits || {}; - const mains = status?.mains || {}; + const circuits = status?.circuits ?? {}; + const mains = status?.mains ?? {}; // Discover notify targets from three sources, deduplicated: // 1. Mobile app services derived from person entity device_trackers // 2. Entity-based notify targets (notify.*) from hass.states // 3. Legacy service-based targets from hass.services.notify - const targetSet = new Set(); + const targetSet = new Set(); // Derive mobile app notify services from person entities + device_trackers for (const [eid, stateObj] of Object.entries(hass.states || {})) { if (!eid.startsWith("person.")) continue; - const trackers = stateObj.attributes?.device_trackers || []; + const trackers = stateObj.attributes?.device_trackers as string[] | undefined; + if (!trackers) continue; for (const tracker of trackers) { const deviceName = tracker.split(".")[1]; if (deviceName) targetSet.add(`notify.mobile_app_${deviceName}`); @@ -112,23 +119,23 @@ export class MonitoringTab { const allNotifyTargets = [...targetSet].sort(); - const rawTargets = globalSettings.notify_targets || "notify.notify"; - const selectedTargets = (typeof rawTargets === "string" ? rawTargets.split(",") : rawTargets).map(s => s.trim()).filter(Boolean); - const titleTemplate = globalSettings.notification_title_template || "SPAN: {name} {alert_type}"; - const messageTemplate = globalSettings.notification_message_template || "{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)"; + const rawTargets = globalSettings.notify_targets ?? "notify.notify"; + const selectedTargets = (typeof rawTargets === "string" ? rawTargets.split(",") : rawTargets).map((s: string) => s.trim()).filter(Boolean); + const titleTemplate = globalSettings.notification_title_template ?? "SPAN: {name} {alert_type}"; + const messageTemplate = globalSettings.notification_message_template ?? "{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)"; const persistentNotifications = globalSettings.enable_persistent_notifications !== false; const eventBus = globalSettings.enable_event_bus !== false; - const currentPriority = globalSettings.notification_priority || "default"; + const currentPriority = globalSettings.notification_priority ?? "default"; - const circuitEntries = Object.entries(circuits).sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || "")); + const circuitEntries = Object.entries(circuits).sort(([, a], [, b]) => (a.name ?? "").localeCompare(b.name ?? "")); const mainsEntries = Object.entries(mains); - const allPoints = [...circuitEntries, ...mainsEntries]; + const allPoints: [string, MonitoringPointInfo][] = [...circuitEntries, ...mainsEntries]; const allEnabled = allPoints.length > 0 && allPoints.every(([, c]) => c.monitoring_enabled !== false); const someEnabled = allPoints.some(([, c]) => c.monitoring_enabled !== false); const circuitRows = circuitEntries .map(([entityId, info]) => { - const name = escapeHtml(info.name || entityId); + const name = escapeHtml(info.name ?? entityId); const enabled = info.monitoring_enabled !== false; const hasOverride = info.has_override === true; const dimStyle = enabled ? "" : "opacity:0.4;"; @@ -164,7 +171,7 @@ export class MonitoringTab { const mainsRows = Object.entries(mains) .map(([entityId, info]) => { - const name = escapeHtml(info.name || entityId); + const name = escapeHtml(info.name ?? entityId); const enabled = info.monitoring_enabled !== false; const hasOverride = info.has_override === true; const dimStyle = enabled ? "" : "opacity:0.4;"; @@ -250,7 +257,7 @@ export class MonitoringTab { color:var(--primary-text-color); border-radius:4px;padding:6px 10px;width:100%;font-size:0.85em; text-align:left;cursor:pointer;display:flex;align-items:center;justify-content:space-between;"> - ${selectedTargets.length ? selectedTargets.map(s => escapeHtml(s)).join(", ") : t("notification.none_selected")} + ${selectedTargets.length ? selectedTargets.map((s: string) => escapeHtml(s)).join(", ") : t("notification.none_selected")}
@@ -383,7 +390,7 @@ export class MonitoringTab { `; // Set indeterminate state on toggle-all checkbox - const toggleAllCb = container.querySelector("#toggle-all-circuits"); + const toggleAllCb = container.querySelector("#toggle-all-circuits"); if (toggleAllCb && !allEnabled && someEnabled) { toggleAllCb.indeterminate = true; } @@ -398,12 +405,12 @@ export class MonitoringTab { this._bindResetButtons(container, hass); } - _serviceData(data) { + private _serviceData(data: Record): Record { if (this._configEntryId) data.config_entry_id = this._configEntryId; return data; } - _callSetGlobal(hass, data) { + private _callSetGlobal(hass: HomeAssistant, data: Record): Promise { return hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, @@ -412,51 +419,57 @@ export class MonitoringTab { }); } - _bindGlobalControls(container, hass) { - const enabledCheckbox = container.querySelector("#monitoring-enabled"); - const fieldsDiv = container.querySelector("#global-fields"); - const statusEl = container.querySelector("#global-status"); + private _bindGlobalControls(container: HTMLElement, hass: HomeAssistant): void { + const enabledCheckbox = container.querySelector("#monitoring-enabled"); + const fieldsDiv = container.querySelector("#global-fields"); + const statusEl = container.querySelector("#global-status"); - const saveGlobal = () => { - clearTimeout(this._debounceTimer); + const saveGlobal = (): void => { + if (this._debounceTimer) clearTimeout(this._debounceTimer); this._debounceTimer = setTimeout(async () => { - const data = { - continuous_threshold_pct: parseInt(container.querySelector("#g-continuous").value, 10), - spike_threshold_pct: parseInt(container.querySelector("#g-spike").value, 10), - window_duration_m: parseInt(container.querySelector("#g-window").value, 10), - cooldown_duration_m: parseInt(container.querySelector("#g-cooldown").value, 10), + const data: Record = { + continuous_threshold_pct: parseInt(container.querySelector("#g-continuous")!.value, 10), + spike_threshold_pct: parseInt(container.querySelector("#g-spike")!.value, 10), + window_duration_m: parseInt(container.querySelector("#g-window")!.value, 10), + cooldown_duration_m: parseInt(container.querySelector("#g-cooldown")!.value, 10), }; try { await this._callSetGlobal(hass, data); await this.render(container, hass); - } catch (err) { - statusEl.textContent = `${t("error.prefix")} ${err.message || t("error.failed_save")}`; - statusEl.style.color = "var(--error-color, #f44336)"; + } catch (err: unknown) { + if (statusEl) { + const message = err instanceof Error ? err.message : t("error.failed_save"); + statusEl.textContent = `${t("error.prefix")} ${message}`; + statusEl.style.color = "var(--error-color, #f44336)"; + } } - }, 500); + }, INPUT_DEBOUNCE_MS); }; if (enabledCheckbox) { enabledCheckbox.addEventListener("change", async () => { const enabled = enabledCheckbox.checked; - fieldsDiv.style.opacity = enabled ? "" : "0.4"; - fieldsDiv.style.pointerEvents = enabled ? "" : "none"; - const statusEl2 = container.querySelector("#global-status"); + if (fieldsDiv) { + fieldsDiv.style.opacity = enabled ? "" : "0.4"; + fieldsDiv.style.pointerEvents = enabled ? "" : "none"; + } + const statusEl2 = container.querySelector("#global-status"); try { if (enabled) { - const data = { - continuous_threshold_pct: parseInt(container.querySelector("#g-continuous").value, 10), - spike_threshold_pct: parseInt(container.querySelector("#g-spike").value, 10), - window_duration_m: parseInt(container.querySelector("#g-window").value, 10), - cooldown_duration_m: parseInt(container.querySelector("#g-cooldown").value, 10), + const data: Record = { + continuous_threshold_pct: parseInt(container.querySelector("#g-continuous")!.value, 10), + spike_threshold_pct: parseInt(container.querySelector("#g-spike")!.value, 10), + window_duration_m: parseInt(container.querySelector("#g-window")!.value, 10), + cooldown_duration_m: parseInt(container.querySelector("#g-cooldown")!.value, 10), }; await this._callSetGlobal(hass, data); } else { await this._callSetGlobal(hass, { enabled: false }); } - } catch (err) { + } catch (err: unknown) { if (statusEl2) { - statusEl2.textContent = `${t("error.prefix")} ${err.message || t("error.failed")}`; + const message = err instanceof Error ? err.message : t("error.failed"); + statusEl2.textContent = `${t("error.prefix")} ${message}`; statusEl2.style.color = "var(--error-color, #f44336)"; } return; @@ -465,69 +478,71 @@ export class MonitoringTab { }); } - for (const input of container.querySelectorAll("#global-fields input[type=number]")) { + for (const input of container.querySelectorAll("#global-fields input[type=number]")) { input.addEventListener("input", saveGlobal); } } - _bindNotifyTargetSelect(container, hass) { - const btn = container.querySelector("#notify-target-btn"); - const dropdown = container.querySelector("#notify-target-dropdown"); - const label = container.querySelector("#notify-target-label"); + private _bindNotifyTargetSelect(container: HTMLElement, hass: HomeAssistant): void { + const btn = container.querySelector("#notify-target-btn"); + const dropdown = container.querySelector("#notify-target-dropdown"); + const label = container.querySelector("#notify-target-label"); if (!btn || !dropdown) return; - btn.addEventListener("click", e => { + btn.addEventListener("click", (e: MouseEvent) => { e.stopPropagation(); const isOpen = dropdown.style.display !== "none"; dropdown.style.display = isOpen ? "none" : "block"; }); // Close dropdown when clicking outside - const closeHandler = e => { + const closeHandler = (e: MouseEvent): void => { const selectEl = container.querySelector("#notify-target-select"); - if (selectEl && !selectEl.contains(e.target)) { + if (selectEl && !selectEl.contains(e.target as Node)) { dropdown.style.display = "none"; } }; - document.addEventListener("click", closeHandler); + document.addEventListener("click", closeHandler as EventListener); // Store ref for cleanup on next render (dropdown rebuilt each render) this._notifyCloseHandler = closeHandler; // Handle checkbox changes - for (const cb of container.querySelectorAll(".notify-target-cb")) { + for (const cb of container.querySelectorAll(".notify-target-cb")) { cb.addEventListener("change", () => { - const checked = [...container.querySelectorAll(".notify-target-cb:checked")]; + const checked = [...container.querySelectorAll(".notify-target-cb:checked")]; const targets = checked.map(c => c.value); - label.textContent = targets.length ? targets.join(", ") : t("notification.none_selected"); + if (label) { + label.textContent = targets.length ? targets.join(", ") : t("notification.none_selected"); + } - clearTimeout(this._debounceTimer); + if (this._debounceTimer) clearTimeout(this._debounceTimer); this._debounceTimer = setTimeout(async () => { try { await this._callSetGlobal(hass, { notify_targets: targets.join(", ") }); } catch { // will show on next render } - }, 500); + }, INPUT_DEBOUNCE_MS); }); } } - _bindNotificationSettings(container, hass) { - const persistentCb = container.querySelector("#g-persistent-notifications"); - const eventBusCb = container.querySelector("#g-event-bus"); - const prioritySelect = container.querySelector("#g-priority"); - const titleInput = container.querySelector("#g-title-template"); - const messageInput = container.querySelector("#g-message-template"); + private _bindNotificationSettings(container: HTMLElement, hass: HomeAssistant): void { + const persistentCb = container.querySelector("#g-persistent-notifications"); + const eventBusCb = container.querySelector("#g-event-bus"); + const prioritySelect = container.querySelector("#g-priority"); + const titleInput = container.querySelector("#g-title-template"); + const messageInput = container.querySelector("#g-message-template"); - const saveField = (field, value) => { - clearTimeout(this._debounceTimer); + const saveField = (field: string, value: string | boolean): void => { + if (this._debounceTimer) clearTimeout(this._debounceTimer); this._debounceTimer = setTimeout(async () => { try { await this._callSetGlobal(hass, { [field]: value }); } catch { // will show on next render } - }, 500); + }, INPUT_DEBOUNCE_MS); }; if (persistentCb) { @@ -562,12 +577,17 @@ export class MonitoringTab { } } - _bindToggleAll(container, hass, circuits, mains) { - const toggleAll = container.querySelector("#toggle-all-circuits"); + private _bindToggleAll( + container: HTMLElement, + hass: HomeAssistant, + circuits: Record, + mains: Record + ): void { + const toggleAll = container.querySelector("#toggle-all-circuits"); if (!toggleAll) return; toggleAll.addEventListener("change", async () => { const enabled = toggleAll.checked; - const calls = [ + const calls: Promise[] = [ ...Object.keys(circuits).map(entityId => hass .callWS({ @@ -594,8 +614,8 @@ export class MonitoringTab { }); } - _bindMainsToggles(container, hass) { - for (const cb of container.querySelectorAll(".mains-toggle")) { + private _bindMainsToggles(container: HTMLElement, hass: HomeAssistant): void { + for (const cb of container.querySelectorAll(".mains-toggle")) { cb.addEventListener("change", async () => { const entityId = cb.dataset.entity; const enabled = cb.checked; @@ -618,8 +638,8 @@ export class MonitoringTab { } } - _bindCircuitToggles(container, hass) { - for (const cb of container.querySelectorAll(".circuit-toggle")) { + private _bindCircuitToggles(container: HTMLElement, hass: HomeAssistant): void { + for (const cb of container.querySelectorAll(".circuit-toggle")) { cb.addEventListener("change", async () => { const entityId = cb.dataset.entity; const enabled = cb.checked; @@ -639,12 +659,13 @@ export class MonitoringTab { } } - _bindThresholdInputs(container, hass) { - const timers = new Map(); - for (const input of container.querySelectorAll(".threshold-input")) { + private _bindThresholdInputs(container: HTMLElement, hass: HomeAssistant): void { + const timers = new Map>(); + for (const input of container.querySelectorAll(".threshold-input")) { input.addEventListener("input", () => { const key = `${input.dataset.entity}-${input.dataset.field}`; - clearTimeout(timers.get(key)); + const existing = timers.get(key); + if (existing) clearTimeout(existing); timers.set( key, setTimeout(async () => { @@ -660,21 +681,21 @@ export class MonitoringTab { type: "call_service", domain: INTEGRATION_DOMAIN, service, - service_data: this._serviceData({ [idField]: entityId, [field]: val }), + service_data: this._serviceData({ [idField]: entityId, [field!]: val }), }); // Re-render to update Custom badge and Reset button await this.render(container, hass); } catch { input.style.borderColor = "var(--error-color, #f44336)"; } - }, 800) + }, THRESHOLD_DEBOUNCE_MS) ); }); } } - _bindResetButtons(container, hass) { - for (const btn of container.querySelectorAll(".reset-btn")) { + private _bindResetButtons(container: HTMLElement, hass: HomeAssistant): void { + for (const btn of container.querySelectorAll(".reset-btn")) { btn.addEventListener("click", async () => { const entityId = btn.dataset.entity; const type = btn.dataset.type; diff --git a/src/panel/tab-settings.js b/src/panel/tab-settings.ts similarity index 79% rename from src/panel/tab-settings.js rename to src/panel/tab-settings.ts index 99eeb07..5ec825b 100644 --- a/src/panel/tab-settings.js +++ b/src/panel/tab-settings.ts @@ -1,8 +1,9 @@ -import { INTEGRATION_DOMAIN, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON } from "../constants.js"; +import { INTEGRATION_DOMAIN, GRAPH_HORIZONS, DEFAULT_GRAPH_HORIZON, INPUT_DEBOUNCE_MS } from "../constants.js"; import { escapeHtml } from "../helpers/sanitize.js"; import { t } from "../i18n.js"; +import type { HomeAssistant, PanelTopology, GraphSettings, CallServiceResponse } from "../types.js"; -function horizonOptions(selectedKey) { +function horizonOptions(selectedKey: string): string { return Object.keys(GRAPH_HORIZONS) .map(key => ``) .join(""); @@ -16,43 +17,47 @@ const SELECT_STYLE = ` `; export class SettingsTab { + private _debounceTimers: Map>; + private _configEntryId: string | null; + private _deviceId: string | null; + constructor() { this._debounceTimers = new Map(); this._configEntryId = null; this._deviceId = null; } - stop() { + stop(): void { for (const timer of this._debounceTimers.values()) { clearTimeout(timer); } this._debounceTimers.clear(); } - async render(container, hass, configEntryId, deviceId) { + async render(container: HTMLElement, hass: HomeAssistant, configEntryId?: string, deviceId?: string): Promise { if (configEntryId !== undefined) this._configEntryId = configEntryId; if (deviceId !== undefined) this._deviceId = deviceId; - let graphSettings; + let graphSettings: GraphSettings | null; try { - const serviceData = {}; + const serviceData: Record = {}; if (this._configEntryId) serviceData.config_entry_id = this._configEntryId; - const resp = await hass.callWS({ + const resp = await hass.callWS({ type: "call_service", domain: INTEGRATION_DOMAIN, service: "get_graph_settings", service_data: serviceData, return_response: true, }); - graphSettings = resp?.response || null; + graphSettings = (resp?.response as GraphSettings) || null; } catch { graphSettings = null; } - let topology = null; + let topology: PanelTopology | null = null; try { if (this._deviceId) { - topology = await hass.callWS({ + topology = await hass.callWS({ type: `${INTEGRATION_DOMAIN}/panel_topology`, device_id: this._deviceId, }); @@ -69,9 +74,9 @@ export class SettingsTab { const circuitRows = circuitEntries .map(([uuid, circuit]) => { const name = escapeHtml(circuit.name || uuid); - const circuitData = circuitSettings[uuid] || {}; - const effectiveHorizon = circuitData.horizon ?? globalHorizon; - const hasOverride = circuitData.has_override === true; + const circuitData = circuitSettings[uuid]; + const effectiveHorizon = circuitData?.horizon ?? globalHorizon; + const hasOverride = circuitData?.has_override === true; const safeUuid = escapeHtml(uuid); return ` @@ -150,13 +155,13 @@ export class SettingsTab { this._bindResetButtons(container, hass); } - _serviceData(data) { + private _serviceData(data: Record): Record { if (this._configEntryId) data.config_entry_id = this._configEntryId; return data; } - _bindGlobalHorizon(container, hass) { - const select = container.querySelector("#global-horizon"); + private _bindGlobalHorizon(container: HTMLElement, hass: HomeAssistant): void { + const select = container.querySelector("#global-horizon"); if (!select) return; select.addEventListener("change", async () => { await hass.callWS({ @@ -170,12 +175,13 @@ export class SettingsTab { }); } - _bindCircuitHorizons(container, hass) { - for (const select of container.querySelectorAll(".horizon-select")) { + private _bindCircuitHorizons(container: HTMLElement, hass: HomeAssistant): void { + for (const select of container.querySelectorAll(".horizon-select")) { select.addEventListener("change", () => { const uuid = select.dataset.circuit; const key = `circuit-${uuid}`; - clearTimeout(this._debounceTimers.get(key)); + const existing = this._debounceTimers.get(key); + if (existing) clearTimeout(existing); this._debounceTimers.set( key, setTimeout(async () => { @@ -187,14 +193,14 @@ export class SettingsTab { }); container.dispatchEvent(new CustomEvent("graph-settings-changed", { bubbles: true, composed: true })); await this.render(container, hass); - }, 500) + }, INPUT_DEBOUNCE_MS) ); }); } } - _bindResetButtons(container, hass) { - for (const btn of container.querySelectorAll(".reset-btn")) { + private _bindResetButtons(container: HTMLElement, hass: HomeAssistant): void { + for (const btn of container.querySelectorAll(".reset-btn")) { btn.addEventListener("click", async () => { const uuid = btn.dataset.circuit; await hass.callWS({ diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..862fa9f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,217 @@ +// -- Home Assistant types (subset used by this project) -- + +export interface HassEntity { + entity_id: string; + state: string; + attributes: Record; + last_changed: string; + last_updated: string; +} + +export interface HomeAssistant { + states: Record; + services: Record>; + language: string; + callService: (domain: string, service: string, data?: Record, target?: Record) => Promise; + callWS: (msg: Record) => Promise; + formatEntityState?: (entity: HassEntity) => string; + connection?: { + subscribeEvents: (callback: () => void, event: string) => Promise<() => void>; + }; +} + +// -- SPAN topology types -- + +export interface CircuitEntities { + power?: string; + current?: string; + switch?: string; + select?: string; + [key: string]: string | undefined; +} + +export interface Circuit { + name: string; + tabs: number[]; + entities: CircuitEntities; + breaker_rating_a?: number | null; + device_type?: string; + relay_state?: string; + is_user_controllable?: boolean; + always_on?: boolean; + voltage?: number; +} + +export interface SubDeviceEntityInfo { + domain: string; + original_name?: string; + unique_id?: string; +} + +export interface SubDevice { + name?: string; + type?: string; + entities?: Record; +} + +export interface PanelEntities { + site_power?: string; + current_power?: string; + feedthrough_power?: string; + pv_power?: string; + battery_level?: string; + dsm_state?: string; +} + +export interface PanelTopology { + circuits: Record; + sub_devices?: Record; + panel_entities?: PanelEntities; + device_name?: string; + serial?: string; + firmware?: string; + panel_size?: number; + device_id?: string; +} + +export interface PanelDevice { + id: string; + name?: string; + name_by_user?: string; + config_entries?: string[]; + identifiers?: [string, string][]; + via_device_id?: string | null; + sw_version?: string; + model?: string; +} + +export interface DiscoveryResult { + topology: PanelTopology | null; + panelDevice: PanelDevice | null; + panelSize: number; +} + +// -- Card configuration -- + +export interface CardConfig { + device_id?: string; + history_days?: number; + history_hours?: number; + history_minutes?: number; + chart_metric?: string; + show_panel?: boolean; + show_battery?: boolean; + show_evse?: boolean; + visible_sub_entities?: Record; +} + +// -- Chart & history types -- + +export interface HistoryPoint { + time: number; + value: number; +} + +export type HistoryMap = Map; + +export interface ChartMetricDef { + entityRole: string; + label: () => string; + unit: (v: number) => string; + format: (v: number) => string; + fixedMin?: number; + fixedMax?: number; +} + +// -- Graph settings -- + +export interface CircuitGraphOverride { + horizon: string; + has_override: boolean; +} + +export interface GraphSettings { + global_horizon?: string; + circuits?: Record; + sub_devices?: Record; +} + +// -- Monitoring -- + +export interface MonitoringPointInfo { + name?: string; + monitoring_enabled?: boolean; + utilization_pct?: number; + over_threshold_since?: string | null; + has_override?: boolean; + continuous_threshold_pct?: number; + spike_threshold_pct?: number; + window_duration_m?: number; + cooldown_duration_m?: number; +} + +export interface MonitoringGlobalSettings { + continuous_threshold_pct?: number; + spike_threshold_pct?: number; + window_duration_m?: number; + cooldown_duration_m?: number; + notify_targets?: string | string[]; + notification_title_template?: string; + notification_message_template?: string; + enable_persistent_notifications?: boolean; + enable_event_bus?: boolean; + notification_priority?: string; +} + +export interface MonitoringStatusResponse { + enabled?: boolean; + global_settings?: MonitoringGlobalSettings; + circuits?: Record; + mains?: Record; +} + +export interface CallServiceResponse { + response?: unknown; +} + +export interface MonitoringStatus { + circuits?: Record; + mains?: Record; +} + +// -- Graph horizon preset -- + +export interface GraphHorizonPreset { + ms: number; + refreshMs: number; + useRealtime: boolean; +} + +// -- Shedding priority -- + +export interface SheddingPriorityDef { + icon: string; + icon2?: string; + color: string; + label: () => string; + textLabel?: string; +} + +// -- Sub-device entity collection -- + +export interface SubDeviceEntityRef { + entityId: string; + key: string; + devId: string; +} + +// -- Entity descriptor for sub-device entity finder -- + +export interface EntityDescriptor { + names: string[]; + suffixes: string[]; +} + +// -- Dual-tab layout classification -- + +export type DualTabLayout = "row-span" | "col-span" | null; diff --git a/tests/chart-options.test.ts b/tests/chart-options.test.ts new file mode 100644 index 0000000..1951cd0 --- /dev/null +++ b/tests/chart-options.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect } from "vitest"; +import { buildChartOptions } from "../src/chart/chart-options.js"; +import { CHART_METRICS, BESS_CHART_METRICS } from "../src/constants.js"; +import type { HistoryPoint, ChartMetricDef } from "../src/types.js"; + +const powerMetric = CHART_METRICS["power"]!; +const currentMetric = CHART_METRICS["current"]!; +const socMetric = BESS_CHART_METRICS["soc"]!; + +describe("buildChartOptions", () => { + it("returns valid options with empty history", () => { + const result = buildChartOptions([], 300_000, powerMetric, false, undefined); + expect(result.options).toBeDefined(); + expect(result.series).toHaveLength(1); + expect(result.series[0]!.data).toEqual([]); + }); + + it("returns valid options when history is undefined", () => { + const result = buildChartOptions(undefined, 300_000, powerMetric, false, undefined); + expect(result.series[0]!.data).toEqual([]); + }); + + it("returns correct time axis range", () => { + const durationMs = 300_000; + const before = Date.now(); + const result = buildChartOptions([], durationMs, powerMetric, false, undefined); + const after = Date.now(); + + expect(result.options.xAxis.type).toBe("time"); + expect(result.options.xAxis.max).toBeGreaterThanOrEqual(before); + expect(result.options.xAxis.max).toBeLessThanOrEqual(after); + expect(result.options.xAxis.min).toBeGreaterThanOrEqual(before - durationMs); + expect(result.options.xAxis.min).toBeLessThanOrEqual(after - durationMs); + }); + + it("adds NEC limit lines when breakerRatingA is set and metric is current", () => { + const result = buildChartOptions([], 300_000, currentMetric, false, 20); + // Main data series + continuous load line + trip rating line + expect(result.series).toHaveLength(3); + // Continuous load line (80% of 20A = 16A) + expect(result.series[1]!.data[0]![1]).toBe(16); + // Trip rating line (breaker rating = 20A) + expect(result.series[2]!.data[0]![1]).toBe(20); + // Y-axis max should be ceil(20 * 1.25) = 25 + expect(result.options.yAxis.max).toBe(25); + expect(result.options.yAxis.min).toBe(0); + }); + + it("does not add NEC lines for power metric", () => { + const result = buildChartOptions([], 300_000, powerMetric, false, 20); + expect(result.series).toHaveLength(1); + }); + + it("uses producer accent color when isProducer is true", () => { + const producerResult = buildChartOptions([], 300_000, powerMetric, true, undefined); + const consumerResult = buildChartOptions([], 300_000, powerMetric, false, undefined); + + const producerColor = producerResult.series[0]!.lineStyle.color; + const consumerColor = consumerResult.series[0]!.lineStyle.color; + + expect(producerColor).toBe("rgb(140, 160, 220)"); + expect(consumerColor).toBe("rgb(77, 217, 175)"); + expect(producerColor).not.toBe(consumerColor); + }); + + it("uses fixedMin/fixedMax when metric has them (SoC 0-100)", () => { + const result = buildChartOptions([], 300_000, socMetric, false, undefined); + expect(result.options.yAxis.min).toBe(0); + expect(result.options.yAxis.max).toBe(100); + }); + + it("defaults to power metric when metric is undefined", () => { + const result = buildChartOptions([], 300_000, undefined, false, undefined); + expect(result.options).toBeDefined(); + expect(result.series).toHaveLength(1); + }); + + it("filters history points outside the duration window", () => { + const now = Date.now(); + const history: HistoryPoint[] = [ + { time: now - 600_000, value: 100 }, // outside 5m window + { time: now - 60_000, value: 200 }, // inside window + ]; + const result = buildChartOptions(history, 300_000, powerMetric, false, undefined); + expect(result.series[0]!.data).toHaveLength(1); + expect(result.series[0]!.data[0]![1]).toBe(200); + }); + + it("uses absolute values for data points", () => { + const now = Date.now(); + const history: HistoryPoint[] = [{ time: now - 1000, value: -500 }]; + const result = buildChartOptions(history, 300_000, powerMetric, false, undefined); + expect(result.series[0]!.data[0]![1]).toBe(500); + }); +}); diff --git a/tests/entity-finder.test.ts b/tests/entity-finder.test.ts new file mode 100644 index 0000000..326d3e6 --- /dev/null +++ b/tests/entity-finder.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect } from "vitest"; +import { findSubDevicePowerEntity, findBatteryLevelEntity, findBatterySoeEntity, findBatteryCapacityEntity } from "../src/helpers/entity-finder.js"; +import type { SubDevice } from "../src/types.js"; + +function makeSubDevice(entities: Record): SubDevice { + return { entities } as SubDevice; +} + +describe("findSubDevicePowerEntity", () => { + it("finds power entity by name", () => { + const sub = makeSubDevice({ + "sensor.bess_power": { domain: "sensor", original_name: "Power" }, + }); + expect(findSubDevicePowerEntity(sub)).toBe("sensor.bess_power"); + }); + + it("finds power entity by unique_id suffix", () => { + const sub = makeSubDevice({ + "sensor.bess_1": { domain: "sensor", original_name: "Something", unique_id: "span_bess_power" }, + }); + expect(findSubDevicePowerEntity(sub)).toBe("sensor.bess_1"); + }); + + it("returns null when no power entity exists", () => { + const sub = makeSubDevice({ + "sensor.bess_temp": { domain: "sensor", original_name: "Temperature" }, + }); + expect(findSubDevicePowerEntity(sub)).toBeNull(); + }); + + it("skips non-sensor entities", () => { + const sub = makeSubDevice({ + "switch.bess_power": { domain: "switch", original_name: "Power" }, + }); + expect(findSubDevicePowerEntity(sub)).toBeNull(); + }); +}); + +describe("findBatteryLevelEntity", () => { + it("finds by name", () => { + const sub = makeSubDevice({ + "sensor.batt": { domain: "sensor", original_name: "Battery Level" }, + }); + expect(findBatteryLevelEntity(sub)).toBe("sensor.batt"); + }); + + it("finds by unique_id suffix", () => { + const sub = makeSubDevice({ + "sensor.batt": { domain: "sensor", original_name: "Other", unique_id: "span_battery_level" }, + }); + expect(findBatteryLevelEntity(sub)).toBe("sensor.batt"); + }); +}); + +describe("findBatterySoeEntity", () => { + it("finds state of energy entity", () => { + const sub = makeSubDevice({ + "sensor.soe": { domain: "sensor", original_name: "State of Energy" }, + }); + expect(findBatterySoeEntity(sub)).toBe("sensor.soe"); + }); +}); + +describe("findBatteryCapacityEntity", () => { + it("finds nameplate capacity entity", () => { + const sub = makeSubDevice({ + "sensor.cap": { domain: "sensor", original_name: "Nameplate Capacity" }, + }); + expect(findBatteryCapacityEntity(sub)).toBe("sensor.cap"); + }); +}); diff --git a/tests/format.test.ts b/tests/format.test.ts new file mode 100644 index 0000000..785d31e --- /dev/null +++ b/tests/format.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from "vitest"; +import { formatPowerUnit, formatPowerSigned, formatKw } from "../src/helpers/format.js"; + +describe("formatPowerUnit", () => { + it("returns W for values under 1000", () => { + expect(formatPowerUnit(500)).toBe("W"); + expect(formatPowerUnit(0)).toBe("W"); + expect(formatPowerUnit(999)).toBe("W"); + }); + + it("returns kW for values >= 1000", () => { + expect(formatPowerUnit(1000)).toBe("kW"); + expect(formatPowerUnit(5000)).toBe("kW"); + }); + + it("handles negative values", () => { + expect(formatPowerUnit(-500)).toBe("W"); + expect(formatPowerUnit(-1000)).toBe("kW"); + }); +}); + +describe("formatPowerSigned", () => { + it("formats positive values without sign", () => { + expect(formatPowerSigned(500)).toBe("500"); + expect(formatPowerSigned(1500)).toBe("1.5"); + }); + + it("formats negative values with minus sign", () => { + expect(formatPowerSigned(-500)).toBe("-500"); + expect(formatPowerSigned(-1500)).toBe("-1.5"); + }); + + it("formats zero", () => { + expect(formatPowerSigned(0)).toBe("0"); + }); + + it("formats small values with one decimal", () => { + expect(formatPowerSigned(5)).toBe("5.0"); + }); +}); + +describe("formatKw", () => { + it("converts watts to kW with one decimal", () => { + expect(formatKw(1000)).toBe("1.0"); + expect(formatKw(1500)).toBe("1.5"); + expect(formatKw(2345)).toBe("2.3"); + }); + + it("handles negative values using absolute value", () => { + expect(formatKw(-1500)).toBe("1.5"); + }); + + it("handles zero", () => { + expect(formatKw(0)).toBe("0.0"); + }); +}); diff --git a/tests/graph-settings.test.ts b/tests/graph-settings.test.ts new file mode 100644 index 0000000..4ef4bed --- /dev/null +++ b/tests/graph-settings.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from "vitest"; +import { getEffectiveHorizon, getEffectiveSubDeviceHorizon } from "../src/core/graph-settings.js"; +import type { GraphSettings } from "../src/types.js"; + +describe("getEffectiveHorizon", () => { + it("returns DEFAULT_GRAPH_HORIZON when settings is null", () => { + expect(getEffectiveHorizon(null, "circuit_1")).toBe("5m"); + }); + + it("returns global_horizon when no circuit override exists", () => { + const settings: GraphSettings = { global_horizon: "1h" }; + expect(getEffectiveHorizon(settings, "circuit_1")).toBe("1h"); + }); + + it("returns global_horizon when circuit exists but has_override is false", () => { + const settings: GraphSettings = { + global_horizon: "1h", + circuits: { circuit_1: { horizon: "1d", has_override: false } }, + }; + expect(getEffectiveHorizon(settings, "circuit_1")).toBe("1h"); + }); + + it("returns override horizon when has_override is true", () => { + const settings: GraphSettings = { + global_horizon: "1h", + circuits: { circuit_1: { horizon: "1d", has_override: true } }, + }; + expect(getEffectiveHorizon(settings, "circuit_1")).toBe("1d"); + }); + + it("falls back to DEFAULT_GRAPH_HORIZON when global_horizon is undefined", () => { + const settings: GraphSettings = {}; + expect(getEffectiveHorizon(settings, "circuit_1")).toBe("5m"); + }); +}); + +describe("getEffectiveSubDeviceHorizon", () => { + it("returns DEFAULT_GRAPH_HORIZON when settings is null", () => { + expect(getEffectiveSubDeviceHorizon(null, "sub_1")).toBe("5m"); + }); + + it("returns global_horizon when no sub-device override exists", () => { + const settings: GraphSettings = { global_horizon: "1h" }; + expect(getEffectiveSubDeviceHorizon(settings, "sub_1")).toBe("1h"); + }); + + it("returns global_horizon when sub-device exists but has_override is false", () => { + const settings: GraphSettings = { + global_horizon: "1h", + sub_devices: { sub_1: { horizon: "1w", has_override: false } }, + }; + expect(getEffectiveSubDeviceHorizon(settings, "sub_1")).toBe("1h"); + }); + + it("returns override horizon when has_override is true", () => { + const settings: GraphSettings = { + global_horizon: "1h", + sub_devices: { sub_1: { horizon: "1w", has_override: true } }, + }; + expect(getEffectiveSubDeviceHorizon(settings, "sub_1")).toBe("1w"); + }); + + it("falls back to DEFAULT_GRAPH_HORIZON when global_horizon is undefined", () => { + const settings: GraphSettings = {}; + expect(getEffectiveSubDeviceHorizon(settings, "sub_1")).toBe("5m"); + }); +}); diff --git a/tests/history.test.ts b/tests/history.test.ts new file mode 100644 index 0000000..be3accf --- /dev/null +++ b/tests/history.test.ts @@ -0,0 +1,125 @@ +import { describe, it, expect } from "vitest"; +import { getHistoryDurationMs, getHorizonDurationMs, getMaxHistoryPoints, getMinGapMs, recordSample, deduplicateAndTrim } from "../src/helpers/history.js"; +import type { HistoryMap, HistoryPoint } from "../src/types.js"; + +describe("getHistoryDurationMs", () => { + it("returns default 5 minutes when no config specified", () => { + expect(getHistoryDurationMs({})).toBe(5 * 60 * 1000); + }); + + it("computes from days/hours/minutes", () => { + expect(getHistoryDurationMs({ history_days: 1 })).toBe(24 * 60 * 60 * 1000); + expect(getHistoryDurationMs({ history_hours: 2 })).toBe(2 * 60 * 60 * 1000); + expect(getHistoryDurationMs({ history_minutes: 30 })).toBe(30 * 60 * 1000); + }); + + it("enforces minimum of 60 seconds", () => { + expect(getHistoryDurationMs({ history_minutes: 0 })).toBe(60000); + }); + + it("combines days, hours, and minutes", () => { + const expected = ((1 * 24 + 2) * 60 + 30) * 60 * 1000; + expect(getHistoryDurationMs({ history_days: 1, history_hours: 2, history_minutes: 30 })).toBe(expected); + }); +}); + +describe("getHorizonDurationMs", () => { + it("returns known horizon durations", () => { + expect(getHorizonDurationMs("5m")).toBe(5 * 60 * 1000); + expect(getHorizonDurationMs("1h")).toBe(60 * 60 * 1000); + expect(getHorizonDurationMs("1d")).toBe(24 * 60 * 60 * 1000); + }); + + it("falls back to default for unknown horizons", () => { + expect(getHorizonDurationMs("unknown")).toBe(5 * 60 * 1000); + }); +}); + +describe("getMaxHistoryPoints", () => { + it("returns seconds for durations <= 10 minutes", () => { + expect(getMaxHistoryPoints(5 * 60 * 1000)).toBe(300); + }); + + it("caps at 5000 for large durations", () => { + expect(getMaxHistoryPoints(30 * 24 * 60 * 60 * 1000)).toBe(5000); + }); +}); + +describe("getMinGapMs", () => { + it("returns minimum 500ms", () => { + expect(getMinGapMs(1000)).toBe(500); + }); + + it("scales with duration", () => { + expect(getMinGapMs(60 * 60 * 1000)).toBe(Math.floor(3600000 / 5000)); + }); +}); + +describe("recordSample", () => { + it("creates new entry if key does not exist", () => { + const map: HistoryMap = new Map(); + recordSample(map, "test", 100, 1000, 0, 100); + expect(map.get("test")).toHaveLength(1); + expect(map.get("test")![0]).toEqual({ time: 1000, value: 100 }); + }); + + it("prunes entries older than cutoff", () => { + const map: HistoryMap = new Map(); + map.set("test", [ + { time: 100, value: 1 }, + { time: 200, value: 2 }, + { time: 300, value: 3 }, + ]); + recordSample(map, "test", 4, 400, 250, 100); + const result = map.get("test")!; + expect(result.every(p => p.time >= 250)).toBe(true); + expect(result[result.length - 1]!.value).toBe(4); + }); + + it("enforces maxPoints limit", () => { + const map: HistoryMap = new Map(); + for (let i = 0; i < 10; i++) { + recordSample(map, "test", i, i * 100, 0, 5); + } + expect(map.get("test")!.length).toBeLessThanOrEqual(5); + }); +}); + +describe("deduplicateAndTrim", () => { + it("returns empty array for empty input", () => { + expect(deduplicateAndTrim([], 100)).toEqual([]); + }); + + it("removes points closer than minGapMs", () => { + const points: HistoryPoint[] = [ + { time: 100, value: 1 }, + { time: 200, value: 2 }, + { time: 300, value: 3 }, + { time: 1100, value: 4 }, + ]; + const result = deduplicateAndTrim(points, 100, 500); + expect(result).toHaveLength(2); + expect(result[0]!.time).toBe(100); + expect(result[1]!.time).toBe(1100); + }); + + it("trims to maxPoints", () => { + const points: HistoryPoint[] = Array.from({ length: 20 }, (_, i) => ({ + time: i * 1000, + value: i, + })); + const result = deduplicateAndTrim(points, 5, 0); + expect(result).toHaveLength(5); + }); + + it("sorts unsorted input", () => { + const points: HistoryPoint[] = [ + { time: 300, value: 3 }, + { time: 100, value: 1 }, + { time: 200, value: 2 }, + ]; + const result = deduplicateAndTrim(points, 100, 0); + expect(result[0]!.time).toBe(100); + expect(result[2]!.time).toBe(300); + }); +}); diff --git a/tests/layout.test.ts b/tests/layout.test.ts new file mode 100644 index 0000000..dcf5ce9 --- /dev/null +++ b/tests/layout.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from "vitest"; +import { tabToRow, tabToCol, classifyDualTab } from "../src/helpers/layout.js"; + +describe("tabToRow", () => { + it("maps odd tabs to correct rows", () => { + expect(tabToRow(1)).toBe(1); + expect(tabToRow(3)).toBe(2); + expect(tabToRow(5)).toBe(3); + }); + + it("maps even tabs to correct rows", () => { + expect(tabToRow(2)).toBe(1); + expect(tabToRow(4)).toBe(2); + expect(tabToRow(6)).toBe(3); + }); +}); + +describe("tabToCol", () => { + it("maps odd tabs to left column (0)", () => { + expect(tabToCol(1)).toBe(0); + expect(tabToCol(3)).toBe(0); + expect(tabToCol(5)).toBe(0); + }); + + it("maps even tabs to right column (1)", () => { + expect(tabToCol(2)).toBe(1); + expect(tabToCol(4)).toBe(1); + expect(tabToCol(6)).toBe(1); + }); +}); + +describe("classifyDualTab", () => { + it("returns null for non-dual tabs", () => { + expect(classifyDualTab([1])).toBeNull(); + expect(classifyDualTab([1, 2, 3])).toBeNull(); + }); + + it("returns row-span for adjacent tabs in same row", () => { + expect(classifyDualTab([1, 2])).toBe("row-span"); + expect(classifyDualTab([3, 4])).toBe("row-span"); + }); + + it("returns col-span for tabs in same column", () => { + expect(classifyDualTab([1, 3])).toBe("col-span"); + expect(classifyDualTab([2, 4])).toBe("col-span"); + }); + + it("handles unordered tab arrays", () => { + expect(classifyDualTab([2, 1])).toBe("row-span"); + expect(classifyDualTab([3, 1])).toBe("col-span"); + }); + + it("defaults to row-span for diagonal tabs", () => { + expect(classifyDualTab([1, 4])).toBe("row-span"); + }); +}); diff --git a/tests/monitoring-status.test.ts b/tests/monitoring-status.test.ts new file mode 100644 index 0000000..eb818c0 --- /dev/null +++ b/tests/monitoring-status.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect } from "vitest"; +import { getCircuitMonitoringInfo, hasCustomOverrides, getUtilizationClass, isAlertActive } from "../src/core/monitoring-status.js"; +import type { MonitoringPointInfo, MonitoringStatus } from "../src/types.js"; + +describe("getCircuitMonitoringInfo", () => { + it("returns null when status is null", () => { + expect(getCircuitMonitoringInfo(null, "sensor.circuit_1_power")).toBeNull(); + }); + + it("returns null when circuits map is missing", () => { + const status: MonitoringStatus = {}; + expect(getCircuitMonitoringInfo(status, "sensor.circuit_1_power")).toBeNull(); + }); + + it("returns null when entity is not in circuits map", () => { + const status: MonitoringStatus = { circuits: {} }; + expect(getCircuitMonitoringInfo(status, "sensor.circuit_1_power")).toBeNull(); + }); + + it("returns monitoring info when entity exists", () => { + const info: MonitoringPointInfo = { utilization_pct: 55, monitoring_enabled: true }; + const status: MonitoringStatus = { + circuits: { "sensor.circuit_1_power": info }, + }; + expect(getCircuitMonitoringInfo(status, "sensor.circuit_1_power")).toBe(info); + }); +}); + +describe("hasCustomOverrides", () => { + it("returns false when info is null", () => { + expect(hasCustomOverrides(null)).toBe(false); + }); + + it("returns false when continuous_threshold_pct is undefined", () => { + const info: MonitoringPointInfo = { utilization_pct: 50 }; + expect(hasCustomOverrides(info)).toBe(false); + }); + + it("returns true when continuous_threshold_pct is defined", () => { + const info: MonitoringPointInfo = { continuous_threshold_pct: 80 }; + expect(hasCustomOverrides(info)).toBe(true); + }); +}); + +describe("getUtilizationClass", () => { + it("returns empty string when info is null", () => { + expect(getUtilizationClass(null)).toBe(""); + }); + + it("returns empty string when utilization_pct is undefined", () => { + const info: MonitoringPointInfo = {}; + expect(getUtilizationClass(info)).toBe(""); + }); + + it("returns empty string when utilization_pct is 0", () => { + const info: MonitoringPointInfo = { utilization_pct: 0 }; + expect(getUtilizationClass(info)).toBe(""); + }); + + it("returns utilization-normal for pct below 80", () => { + const info: MonitoringPointInfo = { utilization_pct: 50 }; + expect(getUtilizationClass(info)).toBe("utilization-normal"); + }); + + it("returns utilization-warning for pct at 80", () => { + const info: MonitoringPointInfo = { utilization_pct: 80 }; + expect(getUtilizationClass(info)).toBe("utilization-warning"); + }); + + it("returns utilization-warning for pct between 80 and 99", () => { + const info: MonitoringPointInfo = { utilization_pct: 95 }; + expect(getUtilizationClass(info)).toBe("utilization-warning"); + }); + + it("returns utilization-alert for pct at 100", () => { + const info: MonitoringPointInfo = { utilization_pct: 100 }; + expect(getUtilizationClass(info)).toBe("utilization-alert"); + }); + + it("returns utilization-alert for pct above 100", () => { + const info: MonitoringPointInfo = { utilization_pct: 120 }; + expect(getUtilizationClass(info)).toBe("utilization-alert"); + }); +}); + +describe("isAlertActive", () => { + it("returns false when info is null", () => { + expect(isAlertActive(null)).toBe(false); + }); + + it("returns false when over_threshold_since is null", () => { + const info: MonitoringPointInfo = { over_threshold_since: null }; + expect(isAlertActive(info)).toBe(false); + }); + + it("returns false when over_threshold_since is undefined", () => { + const info: MonitoringPointInfo = {}; + expect(isAlertActive(info)).toBe(false); + }); + + it("returns true when over_threshold_since is a timestamp string", () => { + const info: MonitoringPointInfo = { over_threshold_since: "2026-04-01T12:00:00Z" }; + expect(isAlertActive(info)).toBe(true); + }); +}); diff --git a/tests/sanitize.test.ts b/tests/sanitize.test.ts new file mode 100644 index 0000000..a339306 --- /dev/null +++ b/tests/sanitize.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from "vitest"; +import { escapeHtml } from "../src/helpers/sanitize.js"; + +describe("escapeHtml", () => { + it("escapes ampersands", () => { + expect(escapeHtml("a&b")).toBe("a&b"); + }); + + it("escapes angle brackets", () => { + expect(escapeHtml("